Compare commits

..

33 Commits

Author SHA1 Message Date
c8ffa4c530 修改:pom 打包信息 2024-07-24 12:09:40 +08:00
2b296e73c3 更新 'pom.xml' 2023-06-17 02:27:31 +08:00
c0b8f3842d 更新 'pom.xml' 2023-06-17 01:56:05 +08:00
14276a6c02 更新 'pom.xml' 2023-06-17 01:53:46 +08:00
姜阳琳
8f3ccc8435 修改:模板加载加try catch, 打印出错误文件名 2021-11-02 12:01:14 +08:00
lxy
61b0233918 新增:TplKit加入,参照修改于 SqlKit 2020-05-09 22:50:28 +08:00
James
11b6715082 enjoy 4.9 2020-03-04 17:28:41 +08:00
James
aea176b589 enjoy 4.9 2020-03-04 15:13:22 +08:00
James
04332bab30 enjoy 4.8 release ^_^ 2019-12-10 01:33:19 +08:00
James
a0dfe76bb1 enjoy 4.8 release ^_^ 2019-12-10 01:28:18 +08:00
James
468f75b39e enjoy 4.8 2019-11-09 17:54:10 +08:00
James
b4af3f62f7 enjoy 4.8 2019-11-07 18:01:03 +08:00
James
0f27710991 enjoy 4.7 release ^_^ 2019-10-30 00:06:13 +08:00
James
c4ef9561bc enjoy 4.6 2019-10-30 00:05:01 +08:00
James
1996b12013 enjoy 4.7 release ^_^ 2019-10-30 00:01:20 +08:00
James
4069806028 enjoy 4.6 release ^_^ 2019-10-13 22:36:49 +08:00
James
8cf09eac35 enjoy 4.5 2019-09-01 22:27:20 +08:00
James
550ddaba53 enjoy 4.5 2019-09-01 22:02:46 +08:00
James
260dd4b438 enjoy 4.4 release ^_^ 2019-08-20 21:32:56 +08:00
James
442a920366 enjoy 4.4 release ^_^ 2019-08-20 21:29:19 +08:00
James
6156051e16 enjoy 4.4 2019-08-14 11:49:56 +08:00
James
75e7caf0ec 升级 gpg 2019-07-23 16:06:34 +08:00
James
4a5cfe5ed5 [maven-release-plugin] prepare for next development iteration 2019-07-03 11:55:42 +08:00
James
8bdd4719c8 [maven-release-plugin] prepare release enjoy-4.3 2019-07-03 11:55:38 +08:00
James
1939fb4cc0 enjoy 4.3 release ^_^ 2019-07-03 11:35:34 +08:00
James
c88f7baad9 enjoy 4.2 2019-06-05 10:19:08 +08:00
James
5649576dde [maven-release-plugin] prepare for next development iteration 2019-06-02 22:07:57 +08:00
James
2224e0d212 [maven-release-plugin] prepare release enjoy-4.2 2019-06-02 22:07:53 +08:00
James
1a6006fe36 jfinal enjoy 4.2 2019-06-02 22:04:57 +08:00
James
12c3b5fdb5 jfinal enjoy 4.1 release ^_^ 2019-05-30 22:52:36 +08:00
James
f065faf4e7 [maven-release-plugin] prepare for next development iteration 2019-05-29 21:56:23 +08:00
James
b4e277fcc0 [maven-release-plugin] prepare release enjoy-4.1 2019-05-29 21:56:19 +08:00
James
bbc1e24b87 [maven-release-plugin] prepare for next development iteration 2019-05-21 22:57:57 +08:00
46 changed files with 1968 additions and 349 deletions

2
.gitignore vendored
View File

@ -52,3 +52,5 @@ a_little_config_pro.txt
dev_plan.txt dev_plan.txt

View File

@ -192,5 +192,3 @@ third-party archives.

View File

@ -1,21 +1,73 @@
### Enjoy ### Enjoy
Enjoy 是基于 Java 语言的极轻量极魔板引擎。极轻量级仅 171K 并且不依赖任何第三方。极简设计仅 if、for、switch、set、define、include、render 七个核心指令,让学习成本低到极致。独创 DKFF(Dynamic Key Feature Forward) 词法分析算法与 DLRD (Double Layer Recursive Descent)语法分析算法,避免使用 javacc、antlr、jflex 生成器,令代码量少到极致。 Enjoy 是基于 Java 语言的极轻量极魔板引擎。极轻量级仅 227 KB 并且不依赖任何第三方。极简设计仅 if、for、switch、set、define、include、render 七个核心指令,让学习成本低到极致。独创 DKFF(Dynamic Key Feature Forward) 词法分析算法与 DLRD (Double Layer Recursive Descent)语法分析算法,避免使用 javacc、antlr、jflex 生成器,令代码量少到极致。
#### Maven 坐标
```java
<dependency>
<groupId>com.jfinal</groupId>
<artifactId>enjoy</artifactId>
<version>4.8</version>
</dependency>
```
#### Enjoy 主要特点 #### Enjoy 主要特点
- 消灭传统模板引擎中大量繁杂概念,仅七个核心指令,学习成本极低 - 消灭传统模板引擎中大量繁杂概念,仅七个核心指令,学习成本极低
- 独创 DKFF 词法分析算法与 DLRD 语法分析算法,避免使用 javacc、antlr - 独创 DKFF 词法分析算法与 DLRD 语法分析算法,避免使用 javacc、antlr
- 功能强大,极为简单覆盖掉 freemarker、velocity 的核心功能
- 扩展性强,支持多种扩展方式,且是唯一支持指令级扩展的模板引擎
- 与 java 打通式设计,在模板中与 java 交互极为方便 - 与 java 打通式设计,在模板中与 java 交互极为方便
- 贴近 java 使用直觉,为 java 开发者量身打造 - 贴近 java 使用直觉,为 java 开发者量身打造
- 功能强大,极为简单覆盖掉 freemarker、velocity 的核心功能
- 扩展性强,支持多种扩展方式,且是唯一支持指令级扩展的模板引擎
- 回归模板引擎渲染 View 数据的本质,采用指令式设计,避免 view 层表达复杂逻辑 - 回归模板引擎渲染 View 数据的本质,采用指令式设计,避免 view 层表达复杂逻辑
- 体积小,仅 171K且不依赖于任何第三方 - 体积小,仅 227 KB,且不依赖于任何第三方
#### 简单示例: #### 简单示例:
**1. 在 spring 中的配置** **1. 与 Spring boot 整合**
```java
@Configuration
public class SpringBootConfig {
@Bean(name = "jfinalViewResolver")
public JFinalViewResolver getJFinalViewResolver() {
// 创建用于整合 spring boot 的 ViewResolver 扩展对象
JFinalViewResolver jfr = new JFinalViewResolver();
// 对 spring boot 进行配置
jfr.setSuffix(".html");
jfr.setContentType("text/html;charset=UTF-8");
jfr.setOrder(0);
// 获取 engine 对象,对 enjoy 模板引擎进行配置,配置方式与前面章节完全一样
Engine engine = JFinalViewResolver.engine;
// 热加载配置能对后续配置产生影响,需要放在最前面
engine.setDevMode(true);
// 使用 ClassPathSourceFactory 从 class path 与 jar 包中加载模板文件
engine.setToClassPathSourceFactory();
// 在使用 ClassPathSourceFactory 时要使用 setBaseTemplatePath
// 代替 jfr.setPrefix("/view/")
engine.setBaseTemplatePath("/view/");
// 添加模板函数
engine.addSharedFunction("/common/_layout.html");
engine.addSharedFunction("/common/_paginate.html");
// 更多配置与前面章节完全一样
// engine.addDirective(...)
// engine.addSharedMethod(...);
return jfr;
}
}
```
**2. 与 Spring MVC 整合**
```java ```java
<bean id="viewResolver" class="com.jfinal.template.ext.spring.JFinalViewResolver"> <bean id="viewResolver" class="com.jfinal.template.ext.spring.JFinalViewResolver">
@ -33,7 +85,7 @@ Enjoy 是基于 Java 语言的极轻量极魔板引擎。极轻量级仅 171K
</bean> </bean>
``` ```
**2.详细使用方法见 jfinal 手册** **3.详细使用方法见官方文档**
read me 正在补充,详细使用文档见官网:[https://www.jfinal.com/doc/6-1](https://www.jfinal.com/doc/6-1) read me 正在补充,详细使用文档见官网:[https://www.jfinal.com/doc/6-1](https://www.jfinal.com/doc/6-1)

71
pom.xml
View File

@ -1,12 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.jfinal</groupId> <groupId>com.jfinal</groupId>
<artifactId>enjoy</artifactId> <artifactId>enjoy</artifactId>
<version>4.8.dev</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>enjoy</name> <name>enjoy</name>
<version>4.0</version>
<url>http://www.jfinal.com</url>
<description>Enjoy is a simple, light, rapid, independent, extensible Java Template Engine.</description> <description>Enjoy is a simple, light, rapid, independent, extensible Java Template Engine.</description>
<url>http://www.jfinal.com</url>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@ -17,13 +21,15 @@
<system>Git Issue</system> <system>Git Issue</system>
<url>https://gitee.com/jfinal/enjoy/issues</url> <url>https://gitee.com/jfinal/enjoy/issues</url>
</issueManagement> </issueManagement>
<licenses> <licenses>
<license> <license>
<name>The Apache Software License, Version 2.0</name> <name>The Apache Software License, Version 2.0</name>
<url>http://apache.org/licenses/LICENSE-2.0.txt</url> <url>http://apache.org/licenses/LICENSE-2.0.txt</url>
</license> </license>
</licenses> </licenses>
<developers>
<!--<developers>
<developer> <developer>
<id>jfinal</id> <id>jfinal</id>
<name>James</name> <name>James</name>
@ -31,25 +37,29 @@
<url>http://jfinal.com/user/1</url> <url>http://jfinal.com/user/1</url>
</developer> </developer>
</developers> </developers>
<scm> <scm>
<connection>scm:git:git@gitee.com:jfinal/enjoy.git</connection> <connection>scm:git:git@gitee.com:jfinal/enjoy.git</connection>
<developerConnection>scm:git:git@gitee.com:jfinal/enjoy.git</developerConnection> <developerConnection>scm:git:git@gitee.com:jfinal/enjoy.git</developerConnection>
<url>git@gitee.com:jfinal/enjoy.git</url> <url>git@gitee.com:jfinal/enjoy.git</url>
</scm> </scm>-->
<parent> <distributionManagement>
<groupId>org.sonatype.oss</groupId> <repository>
<artifactId>oss-parent</artifactId> <id>mvn-release</id>
<version>7</version> <name>mvn-release</name>
</parent> <url>https://nexus.1216.top/repository/maven-releases/</url>
</repository>
</distributionManagement>
<repositories> <repositories>
</repositories> </repositories>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>4.8.2</version> <version>4.13.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -67,6 +77,7 @@
</dependencies> </dependencies>
<build> <build>
<defaultGoal>compile</defaultGoal>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
@ -75,20 +86,27 @@
<configuration> <configuration>
<source>1.8</source> <source>1.8</source>
<target>1.8</target> <target>1.8</target>
<encoding>UTF-8</encoding>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.3</version> <version>2.10.4</version>
<configuration> <configuration>
<!-- 解决 java8 发布到 maven 异常 --> <!-- 解决 java8 发布到 maven 异常 -->
<additionalparam>-Xdoclint:none</additionalparam> <additionalparam>-Xdoclint:none</additionalparam>
<encoding>UTF-8</encoding> <encoding>UTF-8</encoding>
<outputDirectory>${basedir}</outputDirectory>
<reportOutputDirectory>${basedir}</reportOutputDirectory>
</configuration> </configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin> </plugin>
<!-- 安装源码到本地仓库 --> <!-- 安装源码到本地仓库 -->
@ -107,24 +125,23 @@
</executions> </executions>
</plugin> </plugin>
<plugin> <!--<plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId> <artifactId>maven-gpg-plugin</artifactId>
<version>1.1</version> <version>1.6</version>
<executions> <executions>
<execution> <execution>
<id>sign-artifacts</id> <id>sign-artifacts</id>
<phase>verify</phase> <phase>verify</phase>
<goals> <goals>
<goal>sign</goal> <goal>sign</goal>
</goals> </goals>
</execution> </execution>
</executions> </executions>
<configuration> </plugin>-->
<skip>false</skip>
</configuration>
</plugin>
</plugins> </plugins>
</build> </build>
</project> </project>

View File

@ -102,6 +102,10 @@ public class StrKit {
return true; return true;
} }
public static String defaultIfBlank(String str, String defaultValue) {
return isBlank(str) ? defaultValue : str;
}
public static String toCamelCase(String stringWithUnderline) { public static String toCamelCase(String stringWithUnderline) {
if (stringWithUnderline.indexOf('_') == -1) { if (stringWithUnderline.indexOf('_') == -1) {
return stringWithUnderline; return stringWithUnderline;
@ -145,6 +149,17 @@ public class StrKit {
return sb.toString(); return sb.toString();
} }
public static String join(java.util.List<String> list, String separator) {
StringBuilder sb = new StringBuilder();
for (int i=0, len=list.size(); i<len; i++) {
if (i > 0) {
sb.append(separator);
}
sb.append(list.get(i));
}
return sb.toString();
}
public static boolean slowEquals(String a, String b) { public static boolean slowEquals(String a, String b) {
byte[] aBytes = (a != null ? a.getBytes() : null); byte[] aBytes = (a != null ? a.getBytes() : null);
byte[] bBytes = (b != null ? b.getBytes() : null); byte[] bBytes = (b != null ? b.getBytes() : null);

View File

@ -0,0 +1,215 @@
/**
* Copyright (c) 2011-2019, James Zhan 詹波 (jfinal@126.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.kit;
import com.jfinal.kit.tpl.NameSpaceDirective;
import com.jfinal.kit.tpl.ParaDirective;
import com.jfinal.kit.tpl.TplDirective;
import com.jfinal.kit.tpl.TplSource;
import com.jfinal.template.Engine;
import com.jfinal.template.Template;
import com.jfinal.template.source.FileSource;
import com.jfinal.template.source.ISource;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
/**
* SqlKit
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class TplKit {
public static String baseSqlTemplatePath;
public static final String SQL_TEMPLATE_MAP_KEY = "_SQL_TEMPLATE_MAP_";
public static final String SQL_PARA_KEY = "_SQL_PARA_";
public static final String PARA_ARRAY_KEY = "_PARA_ARRAY_"; // 此参数保持不动已被用于模板取值 _PARA_ARRAY_[n]
private String configName;
private boolean devMode;
private Engine engine;
private List<TplSource> tplSourceList = new ArrayList<TplSource>();
private Map<String, Template> sqlTemplateMap;
private static Map<String, TplKit> kitMap = new HashMap<>();
public static TplKit use(String configName) {
return use(configName, false);
}
public static synchronized TplKit use(String configName, boolean devMode) {
TplKit tplKit = kitMap.get(configName);
if (tplKit == null) {
tplKit = new TplKit(configName, devMode);
kitMap.put(configName, tplKit);
}
return tplKit;
}
private TplKit(String configName, boolean devMode) {
this.configName = configName;
this.devMode = devMode;
engine = new Engine(configName);
engine.setDevMode(devMode);
engine.setToClassPathSourceFactory();
engine.addDirective("namespace", NameSpaceDirective.class);
engine.addDirective("tpl", TplDirective.class);
engine.addDirective("para", ParaDirective.class, true);
engine.addDirective("p", ParaDirective.class, true); // 配置 #para 指令的别名指令 #p不建议使用在此仅为兼容 3.0 版本
}
private TplKit(String configName) {
this(configName, false);
}
public Engine getEngine() {
return engine;
}
public void setDevMode(boolean devMode) {
this.devMode = devMode;
engine.setDevMode(devMode);
}
public void setBaseTplTemplatePath(String baseSqlTemplatePath) {
this.baseSqlTemplatePath = baseSqlTemplatePath;
}
public void addTplTemplate(String sqlTemplate) {
if (StrKit.isBlank(sqlTemplate)) {
throw new IllegalArgumentException("tplTemplate can not be blank");
}
if (baseSqlTemplatePath != null) {
addTplTemplate(new FileSource(baseSqlTemplatePath, "tpl.sql"));
} else {
tplSourceList.add(new TplSource(sqlTemplate));
}
}
public void addTplTemplate(ISource sqlTemplate) {
if (sqlTemplate == null) {
throw new IllegalArgumentException("sqlTemplate can not be null");
}
tplSourceList.add(new TplSource(sqlTemplate));
}
public void addTplTemplate(File file) {
addTplTemplate(file, null);
}
public void addTplTemplate(File file, FileFilter filter) {
if (file.isFile()) {
if (filter != null && !filter.accept(file)) {
return;
}
addTplTemplate(new FileSource(file.getParent(), file.getName()));
} else if (file.isDirectory()) {
File[] files = file.listFiles();
for (File file1 : files) {
addTplTemplate(file1, filter);
}
}
}
public synchronized void parseTplTemplate() {
Map<String, Template> sqlTemplateMap = new HashMap<String, Template>(512, 0.5F);
for (TplSource ss : tplSourceList) {
String fileName = ss.isFile() ? ss.file : ((FileSource) ss.source).getFinalFileName();
try {
Template template = ss.isFile() ? engine.getTemplate(ss.file) : engine.getTemplate(ss.source);
Map<Object, Object> data = new HashMap<>();
data.put(SQL_TEMPLATE_MAP_KEY, sqlTemplateMap);
template.renderToString(data);
} catch (Exception e) {
throw new RuntimeException(String.format("%s parse error", fileName), e);
}
}
this.sqlTemplateMap = sqlTemplateMap;
}
private void reloadModifiedTplTemplate() {
engine.removeAllTemplateCache(); // 去除 Engine 中的缓存以免 get 出来后重新判断 isModified
parseTplTemplate();
}
private boolean isTplTemplateModified() {
for (Template template : sqlTemplateMap.values()) {
if (template.isModified()) {
return true;
}
}
return false;
}
private Template getTplTemplate(String key) {
Template template = sqlTemplateMap.get(key);
if (template == null) { // if 分支处理起初没有定义但后续不断追加 sql 的情况
if (!devMode) {
return null;
}
if (isTplTemplateModified()) {
synchronized (this) {
if (isTplTemplateModified()) {
reloadModifiedTplTemplate();
template = sqlTemplateMap.get(key);
}
}
}
return template;
}
if (devMode && template.isModified()) {
synchronized (this) {
template = sqlTemplateMap.get(key);
if (template.isModified()) {
reloadModifiedTplTemplate();
template = sqlTemplateMap.get(key);
}
}
}
return template;
}
public String getTpl(String key) {
Template template = getTplTemplate(key);
return template != null ? template.renderToString(null).replaceAll("[\\s]+", " ") : null;
}
public String getTpl(String key, Map para) {
Template template = getTplTemplate(key);
return template != null ? template.renderToString(para).replaceAll("[\\s]+", " ") : null;
}
public String toString() {
return "SqlKit for config : " + configName;
}
}

View File

@ -0,0 +1,74 @@
/**
* Copyright (c) 2011-2019, 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.kit.tpl;
import com.jfinal.template.Directive;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.Const;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
/**
* NameSpaceDirective
*/
public class NameSpaceDirective extends Directive {
static final String NAME_SPACE_KEY = "_NAME_SPACE_";
private String nameSpace;
public void setExprList(ExprList exprList) {
if (exprList.length() == 0) {
throw new ParseException("The parameter of #namespace directive can not be blank", location);
}
if (exprList.length() > 1) {
throw new ParseException("Only one parameter allowed for #namespace directive", location);
}
Expr expr = exprList.getExpr(0);
if (expr instanceof Const && ((Const)expr).isStr()) {
} else {
throw new ParseException("The parameter of #namespace directive must be String", location);
}
this.nameSpace = ((Const)expr).getStr();
}
public void exec(Env env, Scope scope, Writer writer) {
if (scope.get(NAME_SPACE_KEY) != null) {
throw new TemplateException("#namespace directive can not be nested", location);
}
scope.set(NAME_SPACE_KEY, nameSpace);
try {
stat.exec(env, scope, writer);
} finally {
scope.remove(NAME_SPACE_KEY);
}
}
public boolean hasEnd() {
return true;
}
}

View File

@ -0,0 +1,127 @@
/**
* Copyright (c) 2011-2019, 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.kit.tpl;
import com.jfinal.kit.TplKit;
import com.jfinal.template.Directive;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
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.io.Writer;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
/**
* #para 指令用于在 sql 模板中根据参数名生成问号占位以及查询参数
*
* <pre>
* 参数为表达式的用法
* 1模板内容
* #sql("find")
* select * from user where nickName = #para(nickName) and age > #para(age)
* #end
*
* 2 java 代码
* SqlPara sp = getSqlPara("find", Kv.by("nickName", "prettyGirl").set("age", 18));
* user.find(sp)
* 或者
* user.find(sp.getSql(), sp.getPara());
*
* 3以上用法会在 #para(expr) 处生成问号占位字符并且实际的参数放入 SqlPara 对象的参数列表中
* 后续可以通过 sqlPara.getPara() 获取到参数并直接用于查询
*
*
* 参数为 int 型数字的用法
* 1模板内容
* #sql("find")
* select * from user where id > #para(0) and id < #para(1)
* #end
*
* 2 java 代码
* SqlPara sp = getSqlPara("find", 10, 100);
* user.find(sp)
*
* 3以上用法会在 #para(0) #para(1) 处生成问号占位字符并且将 10100 这两个参数放入
* SqlPara 对象的参数列表中后续可以通过 sqlPara.getPara() 获取到参数并直接用于查询
* </pre>
*/
public class ParaDirective extends Directive {
private int index = -1;
private String paraName = null;
private static boolean checkParaAssigned = true;
public static void setCheckParaAssigned(boolean checkParaAssigned) {
ParaDirective.checkParaAssigned = checkParaAssigned;
}
public void setExprList(ExprList exprList) {
if (exprList.length() == 0) {
throw new ParseException("The parameter of #para directive can not be blank", location);
}
if (exprList.length() == 1) {
Expr expr = exprList.getExpr(0);
if (expr instanceof Const && ((Const)expr).isInt()) {
index = ((Const)expr).getInt();
if (index < 0) {
throw new ParseException("The index of para array must greater than -1", location);
}
}
}
if (checkParaAssigned && exprList.getLastExpr() instanceof Id) {
Id id = (Id)exprList.getLastExpr();
paraName = id.getId();
}
this.exprList = exprList;
}
public void exec(Env env, Scope scope, Writer writer) {
TplPara tplPara = (TplPara)scope.get(TplKit.SQL_PARA_KEY);
if (tplPara == null) {
throw new TemplateException("#para directive invoked by getSqlPara(...) method only", location);
}
write(writer, "?");
if (index == -1) {
// #para(paraName) 中的 paraName 没有赋值时抛出异常
// issue: http://www.jfinal.com/feedback/1832
if (checkParaAssigned && paraName != null && !scope.exists(paraName)) {
throw new TemplateException("The parameter \""+ paraName +"\" must be assigned", location);
}
tplPara.addPara(exprList.eval(scope));
} else {
Object[] paras = (Object[])scope.get(TplKit.PARA_ARRAY_KEY);
if (paras == null) {
throw new TemplateException("The #para(" + index + ") directive must invoked by getSqlPara(String, Object...) method", location);
}
if (index >= paras.length) {
throw new TemplateException("The index of #para directive is out of bounds: " + index, location);
}
tplPara.addPara(paras[index]);
}
}
}

View File

@ -0,0 +1,74 @@
/**
* Copyright (c) 2011-2019, 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.kit.tpl;
import com.jfinal.kit.StrKit;
import com.jfinal.kit.TplKit;
import com.jfinal.template.Directive;
import com.jfinal.template.Env;
import com.jfinal.template.Template;
import com.jfinal.template.expr.ast.Const;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
import java.util.Map;
/**
* SqlDirective
*/
public class TplDirective extends Directive {
private String id;
public void setExprList(ExprList exprList) {
if (exprList.length() == 0) {
throw new ParseException("The parameter of #sql directive can not be blank", location);
}
if (exprList.length() > 1) {
throw new ParseException("Only one parameter allowed for #sql directive", location);
}
Expr expr = exprList.getExpr(0);
if (expr instanceof Const && ((Const)expr).isStr()) {
} else {
throw new ParseException("The parameter of #sql directive must be String", location);
}
this.id = ((Const)expr).getStr();
}
@SuppressWarnings("unchecked")
public void exec(Env env, Scope scope, Writer writer) {
String nameSpace = (String)scope.get(NameSpaceDirective.NAME_SPACE_KEY);
String key = StrKit.isBlank(nameSpace) ? id : nameSpace + "." + id;
Map<String, Template> sqlTemplateMap = (Map<String, Template>)scope.get(TplKit.SQL_TEMPLATE_MAP_KEY);
if (sqlTemplateMap.containsKey(key)) {
throw new ParseException("Sql already exists with key : " + key, location);
}
sqlTemplateMap.put(key, new Template(env, stat));
}
public boolean hasEnd() {
return true;
}
}

View File

@ -0,0 +1,71 @@
/**
* Copyright (c) 2011-2019, 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.kit.tpl;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* SqlPara
* 封装查询使用的 sql 与参数主要用于 getSqlPara(...) 返回值
*/
public class TplPara implements Serializable {
private static final long serialVersionUID = -8586448059592782381L;
String sql;
List<Object> paraList;
public TplPara setSql(String sql) {
this.sql = sql;
return this;
}
public TplPara addPara(Object para) {
if (paraList == null) {
paraList = new ArrayList<Object>();
}
paraList.add(para);
return this;
}
public String getSql() {
return sql;
}
public Object[] getPara() {
/*if (paraList == null || paraList.size() == 0) {
return DbKit.NULL_PARA_ARRAY;
} else {
return paraList.toArray(new Object[paraList.size()]);
}*/
return null;
}
public TplPara clear() {
sql = null;
if (paraList != null) {
paraList.clear();
}
return this;
}
public String toString() {
return "Sql: " + sql + "\nPara: " + paraList;
}
}

View File

@ -0,0 +1,29 @@
package com.jfinal.kit.tpl;
import com.jfinal.template.source.ISource;
/**
* 封装 sql 模板源
*/
public class TplSource {
public String file;
public ISource source;
public TplSource(String file) {
this.file = file;
this.source = null;
}
public TplSource(ISource source) {
this.file = null;
this.source = source;
}
public boolean isFile() {
return file != null;
}
}

View File

@ -0,0 +1,100 @@
/**
* Copyright (c) 2011-2019, 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.proxy;
import java.util.Map;
/**
* ProxyClass
*/
public class ProxyClass {
// 被代理的目标
private Class<?> target;
/**
* 以下是代理类信息
*/
private String pkg; // 包名
private String name; // 类名
private String sourceCode; // 源代码
private Map<String, byte[]> byteCode; // 字节码
private Class<?> clazz; // 字节码被 loadClass 后的 Class
// private List<ProxyMethod> proxyMethodList = new ArrayList<>();
public ProxyClass(Class<?> target) {
this.target = target;
this.pkg = target.getPackage().getName();
this.name = target.getSimpleName() + "$$EnhancerByJFinal";
}
/**
* 是否需要代理
*/
// public boolean needProxy() {
// return proxyMethodList.size() > 0;
// }
public Class<?> getTarget() {
return target;
}
public String getPkg() {
return pkg;
}
public String getName() {
return name;
}
public String getSourceCode() {
return sourceCode;
}
public void setSourceCode(String sourceCode) {
this.sourceCode = sourceCode;
}
public Map<String, byte[]> getByteCode() {
return byteCode;
}
public void setByteCode(Map<String, byte[]> byteCode) {
this.byteCode = byteCode;
}
public Class<?> getClazz() {
return clazz;
}
public void setClazz(Class<?> clazz) {
this.clazz = clazz;
}
// public void addProxyMethod(ProxyMethod proxyMethod) {
// proxyMethodList.add(proxyMethod);
// }
// public List<ProxyMethod> getProxyMethodList() {
// return proxyMethodList;
// }
}

View File

@ -0,0 +1,70 @@
/**
* Copyright (c) 2011-2019, 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.proxy;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
/**
* ProxyClassLoader
*/
public class ProxyClassLoader extends ClassLoader {
protected Map<String, byte[]> byteCodeMap = new ConcurrentHashMap<>();
static {
registerAsParallelCapable();
}
public ProxyClassLoader() {
super(getParentClassLoader());
}
protected static ClassLoader getParentClassLoader() {
ClassLoader ret = Thread.currentThread().getContextClassLoader();
return ret != null ? ret : ProxyClassLoader.class.getClassLoader();
}
public Class<?> loadProxyClass(ProxyClass proxyClass) {
for (Entry<String, byte[]> e : proxyClass.getByteCode().entrySet()) {
byteCodeMap.putIfAbsent(e.getKey(), e.getValue());
}
try {
return loadClass(proxyClass.getPkg() + "." + proxyClass.getName());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = byteCodeMap.get(name);
if (bytes != null) {
Class<?> ret = defineClass(name, bytes, 0, bytes.length);
byteCodeMap.remove(name);
return ret;
}
return super.findClass(name);
}
}

View File

@ -0,0 +1,239 @@
/**
* Copyright (c) 2011-2019, 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.proxy;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import javax.tools.DiagnosticCollector;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
// import com.jfinal.log.Log;
/**
* ProxyCompiler
*
* https://www.programcreek.com/java-api-examples/?api=javax.tools.JavaCompiler
*/
public class ProxyCompiler {
// protected static final Log log = Log.getLog(ProxyCompiler.class);
// protected List<String> options = Arrays.asList("-target", "1.8" /*, "-parameters"*/);
protected volatile List<String> options = null;
protected List<String> getOptions() {
if (options != null) {
return options;
}
synchronized (this) {
if (options != null) {
return options;
}
List<String> ret = new ArrayList<>();
ret.add("-target");
ret.add("1.8");
String cp = getClassPath();
if (cp != null && cp.trim().length() != 0) {
ret.add("-classpath");
ret.add(cp);
}
options = ret;
return options;
}
}
/**
* 兼容 tomcat 丢失 class path否则无法编译
*/
protected String getClassPath() {
URLClassLoader classLoader = getURLClassLoader();
if (classLoader == null) {
return null;
}
int index = 0;
boolean isWindows = isWindows();
StringBuilder ret = new StringBuilder();
for (URL url : classLoader.getURLs()) {
if (index++ > 0) {
ret.append(File.pathSeparator);
}
String path = url.getFile();
// 如果是 windows 系统去除前缀字符 '/'
if (isWindows && path.startsWith("/")) {
path = path.substring(1);
}
// 去除后缀字符 '/'
if (path.length() > 1 && (path.endsWith("/") || path.endsWith(File.separator))) {
path = path.substring(0, path.length() - 1);
}
ret.append(path);
}
return ret.toString();
}
protected boolean isWindows() {
String osName = System.getProperty("os.name", "unknown");
return osName.toLowerCase().indexOf("windows") != -1;
}
protected URLClassLoader getURLClassLoader() {
ClassLoader ret = Thread.currentThread().getContextClassLoader();
if (ret == null) {
ret = ProxyCompiler.class.getClassLoader();
}
return (ret instanceof URLClassLoader) ? (URLClassLoader)ret : null;
}
public void compile(ProxyClass proxyClass) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) {
throw new RuntimeException("Can not get javax.tools.JavaCompiler, check whether \"tools.jar\" is in the environment variable CLASSPATH \n" +
"Visit https://jfinal.com/doc/4-8 for details \n");
}
DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
try (MyJavaFileManager javaFileManager = new MyJavaFileManager(compiler.getStandardFileManager(collector, null, null))) {
MyJavaFileObject javaFileObject = new MyJavaFileObject(proxyClass.getName(), proxyClass.getSourceCode());
Boolean result = compiler.getTask(null, javaFileManager, collector, getOptions(), null, Arrays.asList(javaFileObject)).call();
outputCompileError(result, collector);
Map<String, byte[]> ret = new HashMap<>();
for (Entry<String, MyJavaFileObject> e : javaFileManager.fileObjects.entrySet()) {
ret.put(e.getKey(), e.getValue().getByteCode());
}
proxyClass.setByteCode(ret);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected void outputCompileError(Boolean result, DiagnosticCollector<JavaFileObject> collector) {
if (! result) {
// collector.getDiagnostics().forEach(item -> log.error(item.toString()));
collector.getDiagnostics().forEach(item -> System.out.println(item.toString()));
}
}
public ProxyCompiler setCompileOptions(List<String> options) {
Objects.requireNonNull(options, "options can not be null");
this.options = options;
return this;
}
public ProxyCompiler addCompileOption(String option) {
Objects.requireNonNull(option, "option can not be null");
options.add(option);
return this;
}
public static class MyJavaFileObject extends SimpleJavaFileObject {
private String source;
private ByteArrayOutputStream outPutStream;
public MyJavaFileObject(String name, String source) {
super(URI.create("String:///" + name + Kind.SOURCE.extension), Kind.SOURCE);
this.source = source;
}
public MyJavaFileObject(String name, Kind kind) {
super(URI.create("String:///" + name + kind.extension), kind);
source = null;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
if (source == null) {
throw new IllegalStateException("source field can not be null");
}
return source;
}
@Override
public OutputStream openOutputStream() throws IOException {
outPutStream = new ByteArrayOutputStream();
return outPutStream;
}
public byte[] getByteCode() {
return outPutStream.toByteArray();
}
}
public static class MyJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
public Map<String, MyJavaFileObject> fileObjects = new HashMap<>();
public MyJavaFileManager(JavaFileManager fileManager) {
super(fileManager);
}
@Override
public JavaFileObject getJavaFileForOutput(Location location, String qualifiedClassName, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
MyJavaFileObject javaFileObject = new MyJavaFileObject(qualifiedClassName, kind);
fileObjects.put(qualifiedClassName, javaFileObject);
return javaFileObject;
}
// 是否在编译时依赖另一个类的情况下用到本方法 ?
@Override
public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException {
JavaFileObject javaFileObject = fileObjects.get(className);
if (javaFileObject == null) {
javaFileObject = super.getJavaFileForInput(location, className, kind);
}
return javaFileObject;
}
}
}

View File

@ -279,6 +279,11 @@ public class Engine {
return this; return this;
} }
public Engine removeSharedObject(String name) {
config.removeSharedObject(name);
return this;
}
/** /**
* Set output directive factory * Set output directive factory
*/ */
@ -288,23 +293,31 @@ public class Engine {
} }
/** /**
* Add directive * 添加自定义指令
*
* 建议添加自定义指令时明确指定 keepLineBlank 变量值其规则如下
* 1keepLineBlank true 该指令所在行的前后空白字符以及末尾字符 '\n' 将会被保留
* 一般用于具有输出值的指令例如 #date#para 等指令
*
* 2keepLineBlank false 该指令所在行的前后空白字符以及末尾字符 '\n' 将会被删除
* 一般用于没有输出值的指令例如 #for#if#else#end 这种性质的指令
*
* <pre> * <pre>
* 示例 * 示例
* addDirective("now", NowDirective.class) * addDirective("now", NowDirective.class, true)
* </pre> * </pre>
*/ */
public Engine addDirective(String directiveName, Class<? extends Directive> directiveClass) { public Engine addDirective(String directiveName, Class<? extends Directive> directiveClass, boolean keepLineBlank) {
config.addDirective(directiveName, directiveClass); config.addDirective(directiveName, directiveClass, keepLineBlank);
return this; return this;
} }
/** /**
* 该方法已被 addDirective(String, Class<? extends Directive>) 所代替 * 添加自定义指令keepLineBlank 使用默认值
*/ */
@Deprecated public Engine addDirective(String directiveName, Class<? extends Directive> directiveClass) {
public Engine addDirective(String directiveName, Directive directive) { config.addDirective(directiveName, directiveClass);
return addDirective(directiveName, directive.getClass()); return this;
} }
/** /**
@ -473,16 +486,24 @@ public class Engine {
} }
/** /**
* Enjoy 模板引擎对 UTF-8 encoding 做过性能优化某些偏门字符在 * Enjoy 模板引擎对 UTF-8 encoding 做过性能优化某些罕见字符
* 被编码为 UTF-8 时会出现异常此时可以通过继承扩展 EncoderFactory * 无法被编码可以配置为 JdkEncoderFactory 解决问题:
* 来解决编码异常具体用法参考 * engine.setEncoderFactory(new JdkEncoderFactory());
* http://www.jfinal.com/feedback/5340
*/ */
public Engine setEncoderFactory(EncoderFactory encoderFactory) { public Engine setEncoderFactory(EncoderFactory encoderFactory) {
config.setEncoderFactory(encoderFactory); config.setEncoderFactory(encoderFactory);
return this; return this;
} }
/**
* 配置为 JdkEncoderFactory支持 utf8mb4支持 emoji 表情字符
* 支持各种罕见字符编码
*/
public Engine setToJdkEncoderFactory() {
config.setEncoderFactory(new com.jfinal.template.io.JdkEncoderFactory());
return this;
}
public Engine setWriterBufferSize(int bufferSize) { public Engine setWriterBufferSize(int bufferSize) {
config.setWriterBufferSize(bufferSize); config.setWriterBufferSize(bufferSize);
return this; return this;
@ -521,10 +542,10 @@ public class Engine {
* *
* 系统当前默认 FieldGetter 实现类及其位置如下 * 系统当前默认 FieldGetter 实现类及其位置如下
* GetterMethodFieldGetter ---> 调用 getter 方法取值 * GetterMethodFieldGetter ---> 调用 getter 方法取值
* RealFieldGetter ---> 直接获取 public 型的 object.field
* ModelFieldGetter ---> 调用 Model.get(String) 方法取值 * ModelFieldGetter ---> 调用 Model.get(String) 方法取值
* RecordFieldGetter ---> 调用 Record.get(String) 方法取值 * RecordFieldGetter ---> 调用 Record.get(String) 方法取值
* MapFieldGetter ---> 调用 Map.get(String) 方法取值 * MapFieldGetter ---> 调用 Map.get(String) 方法取值
* RealFieldGetter ---> 直接获取 public 型的 object.field
* ArrayLengthGetter ---> 获取数组长度 * ArrayLengthGetter ---> 获取数组长度
* *
* 根据以上次序如果要插入 IsMethodFieldGetter GetterMethodFieldGetter * 根据以上次序如果要插入 IsMethodFieldGetter GetterMethodFieldGetter
@ -551,8 +572,19 @@ public class Engine {
FieldKit.removeFieldGetter(fieldGetterClass); FieldKit.removeFieldGetter(fieldGetterClass);
} }
public static void setToFastFieldKeyBuilder() { public static void setFastFieldKeyBuilder(boolean enable) {
FieldKeyBuilder.setToFastFieldKeyBuilder(); FieldKeyBuilder.setFastFieldKeyBuilder(enable);
}
/**
* 设置极速模式
*
* 极速模式将生成代理对象来消除 java.lang.reflect.Method.invoke(...) 调用
* 性能提升 12.9%
*/
public static void setFastMode(boolean fastMode) {
FieldKit.setFastMode(fastMode);
FieldKeyBuilder.setFastFieldKeyBuilder(fastMode);
} }
} }

View File

@ -19,9 +19,11 @@ package com.jfinal.template;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import com.jfinal.kit.StrKit; import com.jfinal.kit.StrKit;
import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.expr.ast.SharedMethodKit; import com.jfinal.template.expr.ast.SharedMethodKit;
@ -59,6 +61,9 @@ public class EngineConfig {
private Map<String, Class<? extends Directive>> directiveMap = new HashMap<String, Class<? extends Directive>>(64, 0.5F); private Map<String, Class<? extends Directive>> directiveMap = new HashMap<String, Class<? extends Directive>>(64, 0.5F);
private SharedMethodKit sharedMethodKit = new SharedMethodKit(); private SharedMethodKit sharedMethodKit = new SharedMethodKit();
// 保留指令所在行空白字符的指令
private Set<String> keepLineBlankDirectives = new HashSet<>();
private boolean devMode = false; private boolean devMode = false;
private boolean reloadModifiedSharedFunctionInDevMode = true; private boolean reloadModifiedSharedFunctionInDevMode = true;
private String baseTemplatePath = null; private String baseTemplatePath = null;
@ -66,14 +71,19 @@ public class EngineConfig {
private String datePattern = "yyyy-MM-dd HH:mm"; private String datePattern = "yyyy-MM-dd HH:mm";
public EngineConfig() { public EngineConfig() {
// 内置指令 #() #include() 需要配置保留指令所在行前后空白字符以及行尾换行字符 '\n'
setKeepLineBlank("output", true);
setKeepLineBlank("include", true);
// Add official directive of Template Engine // Add official directive of Template Engine
addDirective("render", RenderDirective.class); addDirective("render", RenderDirective.class, true);
addDirective("date", DateDirective.class); addDirective("date", DateDirective.class, true);
addDirective("escape", EscapeDirective.class); addDirective("escape", EscapeDirective.class, true);
addDirective("string", StringDirective.class); addDirective("random", RandomDirective.class, true);
addDirective("random", RandomDirective.class); addDirective("number", NumberDirective.class, true);
addDirective("number", NumberDirective.class);
addDirective("call", CallDirective.class); addDirective("call", CallDirective.class, false);
addDirective("string", StringDirective.class, false);
// Add official shared method of Template Engine // Add official shared method of Template Engine
addSharedMethod(new SharedMethodLib()); addSharedMethod(new SharedMethodLib());
@ -209,10 +219,16 @@ public class EngineConfig {
sharedObjectMap.put(name, object); sharedObjectMap.put(name, object);
} }
Map<String, Object> getSharedObjectMap() { public Map<String, Object> getSharedObjectMap() {
return sharedObjectMap; return sharedObjectMap;
} }
public synchronized void removeSharedObject(String name) {
if (sharedObjectMap != null) {
sharedObjectMap.remove(name);
}
}
/** /**
* Set output directive factory * Set output directive factory
*/ */
@ -312,12 +328,7 @@ public class EngineConfig {
this.reloadModifiedSharedFunctionInDevMode = reloadModifiedSharedFunctionInDevMode; this.reloadModifiedSharedFunctionInDevMode = reloadModifiedSharedFunctionInDevMode;
} }
@Deprecated public synchronized void addDirective(String directiveName, Class<? extends Directive> directiveClass, boolean keepLineBlank) {
public void addDirective(String directiveName, Directive directive) {
addDirective(directiveName, directive.getClass());
}
public synchronized void addDirective(String directiveName, Class<? extends Directive> directiveClass) {
if (StrKit.isBlank(directiveName)) { if (StrKit.isBlank(directiveName)) {
throw new IllegalArgumentException("directive name can not be blank"); throw new IllegalArgumentException("directive name can not be blank");
} }
@ -327,7 +338,15 @@ public class EngineConfig {
if (directiveMap.containsKey(directiveName)) { if (directiveMap.containsKey(directiveName)) {
throw new IllegalArgumentException("directive already exists : " + directiveName); throw new IllegalArgumentException("directive already exists : " + directiveName);
} }
directiveMap.put(directiveName, directiveClass); directiveMap.put(directiveName, directiveClass);
if (keepLineBlank) {
keepLineBlankDirectives.add(directiveName);
}
}
public void addDirective(String directiveName, Class<? extends Directive> directiveClass) {
addDirective(directiveName, directiveClass, false);
} }
public Class<? extends Directive> getDirective(String directiveName) { public Class<? extends Directive> getDirective(String directiveName) {
@ -336,6 +355,19 @@ public class EngineConfig {
public void removeDirective(String directiveName) { public void removeDirective(String directiveName) {
directiveMap.remove(directiveName); directiveMap.remove(directiveName);
keepLineBlankDirectives.remove(directiveName);
}
public void setKeepLineBlank(String directiveName, boolean keepLineBlank) {
if (keepLineBlank) {
keepLineBlankDirectives.add(directiveName);
} else {
keepLineBlankDirectives.remove(directiveName);
}
}
public Set<String> getKeepLineBlankDirectives() {
return keepLineBlankDirectives;
} }
/** /**

View File

@ -102,13 +102,21 @@ public class Template {
} }
} }
/**
* 支持无 data 参数渲染到 String 中去 <br>
* 适用于数据在模板中通过表达式和语句直接计算得出等等应用场景
*/
public String renderToString() {
return renderToString(null);
}
/** /**
* 渲染到 StringBuilder 中去 * 渲染到 StringBuilder 中去
*/ */
public StringBuilder renderToStringBuilder(Map<?, ?> data) { public StringBuilder renderToStringBuilder(Map<?, ?> data) {
FastStringWriter fsw = new FastStringWriter(); FastStringWriter fsw = new FastStringWriter();
render(data, fsw); render(data, fsw);
return fsw.getBuffer(); return fsw.toStringBuilder();
} }
/** /**

View File

@ -0,0 +1,181 @@
package com.jfinal.template.expr.ast;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.jfinal.kit.StrKit;
import com.jfinal.proxy.ProxyClassLoader;
/**
* 使用 jfinal proxy 机制消除 java.lang.reflect.Method.invoke(...)
* 提升性能并且同时支持动态类型的 field 表达式取值
*/
public class FastFieldGetter extends FieldGetter {
protected static ProxyGenerator generator = new ProxyGenerator();
protected static ProxyCompiler compiler = new ProxyCompiler();
protected static ProxyClassLoader classLoader = new ProxyClassLoader();
protected static Map<Class<?>, Proxy> cache = new ConcurrentHashMap<>(512, 0.25F);
protected static boolean outputCompileError = false;
protected Proxy proxy;
protected java.lang.reflect.Method getterMethod;
public FastFieldGetter(Proxy proxy, java.lang.reflect.Method getterMethod) {
this.proxy = proxy;
this.getterMethod = getterMethod;
}
/**
* 仅用于配置 Engine.addFieldGetter(0, new FastFieldGetter());
*/
public FastFieldGetter() {
this(null, null);
}
public FieldGetter takeOver(Class<?> targetClass, String fieldName) {
if (MethodKit.isForbiddenClass(targetClass)) {
throw new RuntimeException("Forbidden class: " + targetClass.getName());
}
String getterName = "get" + StrKit.firstCharToUpperCase(fieldName);
java.lang.reflect.Method[] methodArray = targetClass.getMethods();
for (java.lang.reflect.Method method : methodArray) {
if (method.getName().equals(getterName) && method.getParameterCount() == 0) {
Proxy proxy = cache.get(targetClass);
if (proxy == null) {
synchronized (targetClass) {
proxy = cache.get(targetClass);
if (proxy == null) {
try {
proxy = createProxy(targetClass, fieldName);
} catch (Throwable e) {
return null;
}
cache.putIfAbsent(targetClass, proxy);
}
}
}
return new FastFieldGetter(proxy, method);
}
}
return null;
}
public Object get(Object target, String fieldName) throws Exception {
// return getterMethod.invoke(target, ExprList.NULL_OBJECT_ARRAY);
return proxy.getValue(target, fieldName);
}
protected Proxy createProxy(Class<?> targetClass, String fieldName) {
ProxyClass proxyClass = new ProxyClass(targetClass);
String sourceCode = generator.generate(proxyClass);
// System.out.println(sourceCode);
proxyClass.setSourceCode(sourceCode);
compiler.compile(proxyClass);
Class<?> retClass = classLoader.loadProxyClass(proxyClass);
proxyClass.setClazz(retClass);
try {
return (Proxy)retClass.newInstance();
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public String toString() {
return getterMethod.toString();
}
// ---------
/**
* 代理接口
*/
public static interface Proxy {
public Object getValue(Object target, String fieldName);
}
// ---------
/**
* 代理类
*/
static class ProxyClass extends com.jfinal.proxy.ProxyClass {
private String name; // 类名
public ProxyClass(Class<?> target) {
super(target);
name = target.getSimpleName() + "$$EnhancerByJFinal_FieldGetter";
}
public String getName() {
return name;
}
}
// ---------
/**
* 代理生成器
*/
static class ProxyGenerator {
String generate(ProxyClass proxyClass) {
StringBuilder ret = new StringBuilder(1024);
Class<?> targetClass = proxyClass.getTarget();
String className = proxyClass.getName();
ret.append("package ").append(proxyClass.getPkg()).append(";\n\n");
ret.append("import com.jfinal.template.expr.ast.FastFieldGetter.Proxy;\n\n");
ret.append("public class ").append(className).append(" implements Proxy {\n\n");
ret.append("\tpublic Object getValue(Object target, String fieldName) {\n");
ret.append("\t\tint hash = fieldName.hashCode();\n");
ret.append("\t\tswitch (hash) {\n");
java.lang.reflect.Method[] methodArray = targetClass.getMethods();
for (java.lang.reflect.Method method : methodArray) {
String mn = method.getName();
if (method.getParameterCount() == 0 && mn.startsWith("get") && (!mn.equals("getClass"))) {
String fieldName = StrKit.firstCharToLowerCase(mn.substring(3));
ret.append("\t\tcase ").append(fieldName.hashCode()).append(" :\n");
ret.append("\t\t\treturn ((").append(targetClass.getName()).append(")target).").append(mn).append("();\n");
}
}
ret.append("\t\tdefault :\n");
ret.append("\t\t\tthrow new RuntimeException(\"Can not access the field \\\"\" + target.getClass().getName() + \".\" + fieldName + \"\\\"\");\n");
ret.append("\t\t}\n");
ret.append("\t}\n");
ret.append("}\n");
return ret.toString();
}
}
// ---------
public static void setOutputCompileError(boolean outputCompileError) {
FastFieldGetter.outputCompileError = outputCompileError;
}
/**
* 代理编译器
*/
static class ProxyCompiler extends com.jfinal.proxy.ProxyCompiler {
@Override
protected void outputCompileError(Boolean result, javax.tools.DiagnosticCollector<javax.tools.JavaFileObject> collector) {
if (outputCompileError) {
super.outputCompileError(result, collector);
}
}
}
}

View File

@ -68,7 +68,7 @@ public class FieldGetters {
String getterName = "get" + StrKit.firstCharToUpperCase(fieldName); String getterName = "get" + StrKit.firstCharToUpperCase(fieldName);
java.lang.reflect.Method[] methodArray = targetClass.getMethods(); java.lang.reflect.Method[] methodArray = targetClass.getMethods();
for (java.lang.reflect.Method method : methodArray) { for (java.lang.reflect.Method method : methodArray) {
if (method.getName().equals(getterName) && method.getParameterTypes().length == 0) { if (method.getName().equals(getterName) && method.getParameterCount() == 0) {
// if (MethodKit.isForbiddenMethod(getterName)) { // if (MethodKit.isForbiddenMethod(getterName)) {
// throw new RuntimeException("Forbidden method: " + getterName); // throw new RuntimeException("Forbidden method: " + getterName);
// } // }
@ -115,7 +115,7 @@ public class FieldGetters {
String isMethodName = "is" + StrKit.firstCharToUpperCase(fieldName); String isMethodName = "is" + StrKit.firstCharToUpperCase(fieldName);
java.lang.reflect.Method[] methodArray = targetClass.getMethods(); java.lang.reflect.Method[] methodArray = targetClass.getMethods();
for (java.lang.reflect.Method method : methodArray) { for (java.lang.reflect.Method method : methodArray) {
if (method.getName().equals(isMethodName) && method.getParameterTypes().length == 0) { if (method.getName().equals(isMethodName) && method.getParameterCount() == 0) {
Class<?> returnType = method.getReturnType(); Class<?> returnType = method.getReturnType();
if (returnType == Boolean.class || returnType == boolean.class) { if (returnType == Boolean.class || returnType == boolean.class) {
return new IsMethodFieldGetter(method); return new IsMethodFieldGetter(method);

View File

@ -33,10 +33,14 @@ public abstract class FieldKeyBuilder {
} }
/** /**
* 设置为官方提供的 FastFieldKeyBuilder 实现性能更高 * 开启 FastFieldKeyBuilder性能更高
*/ */
public static void setToFastFieldKeyBuilder() { public static void setFastFieldKeyBuilder(boolean enable) {
instance = new FastFieldKeyBuilder(); if (enable) {
instance = new FastFieldKeyBuilder();
} else {
instance = new StrictFieldKeyBuilder();
}
} }
/** /**

View File

@ -140,6 +140,36 @@ public class FieldKit {
public static void clearCache() { public static void clearCache() {
fieldGetterCache.clear(); fieldGetterCache.clear();
} }
/**
* 设置极速模式
*
* 极速模式将生成代理对象来消除 java.lang.reflect.Method.invoke(...) 调用
* 性能提升 12.9%
*/
public static synchronized void setFastMode(boolean fastMode) {
if (fastMode) {
if ( !contains(FastFieldGetter.class) ) {
addFieldGetterToFirst(new FastFieldGetter());
}
} else {
if (contains(FastFieldGetter.class)) {
removeFieldGetter(FastFieldGetter.class);
}
}
}
/**
* 判断是否包含某个 FieldGetter
*/
public static boolean contains(Class<? extends FieldGetter> fieldGetterClass) {
for (FieldGetter fg : getters) {
if (fg.getClass() == fieldGetterClass) {
return true;
}
}
return false;
}
} }

View File

@ -16,10 +16,6 @@
package com.jfinal.template.expr.ast; 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.TemplateException;
import com.jfinal.template.expr.Sym; import com.jfinal.template.expr.Sym;
import com.jfinal.template.stat.Location; import com.jfinal.template.stat.Location;
@ -37,16 +33,6 @@ public class Logic extends Expr {
private Expr left; // ! 运算没有 left 参数 private Expr left; // ! 运算没有 left 参数
private Expr right; private Expr right;
// 默认为新工作模式
private static boolean newWorkMode = true;
/**
* 设置为旧工作模式为了兼容 jfinal 3.3 之前的版本
*/
@Deprecated
public static void setToOldWorkMode() {
newWorkMode = false;
}
/** /**
* 构造 || && 结点 * 构造 || && 结点
*/ */
@ -107,12 +93,6 @@ public class Logic extends Expr {
* 2boolean 类型原值返回 * 2boolean 类型原值返回
* 3StringStringBuilder 等一切继承自 CharSequence 类的对象返回 length > 0 * 3StringStringBuilder 等一切继承自 CharSequence 类的对象返回 length > 0
* 4其它返回 true * 4其它返回 true
*
* 通过 Logic.setToOldWorkMode() 设置可支持老版本中的以下四个规则
* 1Number 类型返回 value != 0
* 2MapCollection(List被包括在内) 返回 size() > 0
* 3数组返回 length > 0
* 4Iterator 返回 hasNext()
*/ */
public static boolean isTrue(Object v) { public static boolean isTrue(Object v) {
if (v == null) { if (v == null) {
@ -122,38 +102,11 @@ public class Logic extends Expr {
if (v instanceof Boolean) { if (v instanceof Boolean) {
return (Boolean)v; return (Boolean)v;
} }
if (v instanceof CharSequence) { if (v instanceof CharSequence) {
return ((CharSequence)v).length() > 0; return ((CharSequence)v).length() > 0;
} }
// 如果不是新工作模式则对下面类型进行判断
if ( !newWorkMode ) {
if (v instanceof Number) {
if (v instanceof Double) {
return ((Number)v).doubleValue() != 0;
}
if (v instanceof Float) {
return ((Number)v).floatValue() != 0;
}
return ((Number)v).intValue() != 0;
}
// 下面四种类型的判断已提供了 shared method 扩展用法如下
// #if(notEmpty(object)) 以及 #if(isEmpty(object))
if (v instanceof Collection) {
return ((Collection<?>)v).size() > 0;
}
if (v instanceof Map) {
return ((Map<?, ?>)v).size() > 0;
}
if (v.getClass().isArray()) {
return Array.getLength(v) > 0;
}
if (v instanceof Iterator) {
return ((Iterator<?>)v).hasNext();
}
}
return true; return true;
} }

View File

@ -79,7 +79,7 @@ public class Method extends Expr {
try { try {
MethodInfo methodInfo = MethodKit.getMethod(target.getClass(), methodName, argValues); MethodInfo methodInfo = MethodKit.getMethod(target.getClass(), methodName, argValues);
if (methodInfo != null) { if (methodInfo.notNull()) {
return methodInfo.invoke(target, argValues); return methodInfo.invoke(target, argValues);
} }

View File

@ -93,6 +93,29 @@ public class MethodInfo {
} }
return ret.append(")").toString(); return ret.append(")").toString();
} }
// --------- 以下代码仅用于支持 NullMethodInfo
/**
* 仅供 NullMethodInfo 继承使用
*/
protected MethodInfo() {
this.key = null;
this.clazz = null;
this.method = null;
this.isVarArgs = false;
this.paraTypes = null;
}
/**
* 仅仅 NullMethodInfo 会覆盖此方法并返回 false
*
* 1MethodKit.getMethod(...) 消除 instanceof 判断
* 2Method.exec(...) 消除 null 值判断
*/
public boolean notNull() {
return true;
}
} }

View File

@ -41,7 +41,7 @@ public class MethodKit {
private static final Set<String> forbiddenMethods = new HashSet<String>(64); private static final Set<String> forbiddenMethods = new HashSet<String>(64);
private static final Set<Class<?>> forbiddenClasses = new HashSet<Class<?>>(64); private static final Set<Class<?>> forbiddenClasses = new HashSet<Class<?>>(64);
private static final Map<Class<?>, Class<?>> primitiveMap = new HashMap<Class<?>, Class<?>>(64); private static final Map<Class<?>, Class<?>> primitiveMap = new HashMap<Class<?>, Class<?>>(64);
private static final SyncWriteMap<Long, Object> methodCache = new SyncWriteMap<Long, Object>(2048, 0.25F); private static final SyncWriteMap<Long, MethodInfo> methodCache = new SyncWriteMap<Long, MethodInfo>(2048, 0.25F);
// 初始化在模板中调用 method 时所在的被禁止使用类 // 初始化在模板中调用 method 时所在的被禁止使用类
static { static {
@ -49,7 +49,9 @@ public class MethodKit {
System.class, Runtime.class, Thread.class, Class.class, ClassLoader.class, File.class, System.class, Runtime.class, Thread.class, Class.class, ClassLoader.class, File.class,
Compiler.class, InheritableThreadLocal.class, Package.class, Process.class, Compiler.class, InheritableThreadLocal.class, Package.class, Process.class,
RuntimePermission.class, SecurityManager.class, ThreadGroup.class, ThreadLocal.class, RuntimePermission.class, SecurityManager.class, ThreadGroup.class, ThreadLocal.class,
java.lang.reflect.Method.class
java.lang.reflect.Method.class,
java.lang.reflect.Proxy.class
}; };
for (Class<?> c : cs) { for (Class<?> c : cs) {
forbiddenClasses.add(c); forbiddenClasses.add(c);
@ -62,8 +64,8 @@ public class MethodKit {
"getClass", "getDeclaringClass", "forName", "newInstance", "getClassLoader", "getClass", "getDeclaringClass", "forName", "newInstance", "getClassLoader",
"invoke", // "getMethod", "getMethods", // "getField", "getFields", "invoke", // "getMethod", "getMethods", // "getField", "getFields",
"notify", "notifyAll", "wait", "notify", "notifyAll", "wait",
"load", "exit", "loadLibrary", "halt", "exit", "loadLibrary", "halt", // "load",
"stop", "suspend", "resume", "setDaemon", "setPriority", "stop", "suspend", "resume" // "setDaemon", "setPriority"
}; };
for (String m : ms) { for (String m : ms) {
forbiddenMethods.add(m); forbiddenMethods.add(m);
@ -122,36 +124,17 @@ public class MethodKit {
public static MethodInfo getMethod(Class<?> targetClass, String methodName, Object[] argValues) { public static MethodInfo getMethod(Class<?> targetClass, String methodName, Object[] argValues) {
Class<?>[] argTypes = getArgTypes(argValues); Class<?>[] argTypes = getArgTypes(argValues);
Long key = getMethodKey(targetClass, methodName, argTypes); Long key = getMethodKey(targetClass, methodName, argTypes);
Object method = methodCache.get(key); MethodInfo method = methodCache.get(key);
if (method == null) { if (method == null) {
// 已确保不会返回 null对于不存在的 Method只进行一次获取操作
// 提升 null safe 表达式性能未来需要考虑内存泄漏风险
method = doGetMethod(key, targetClass, methodName, argTypes); method = doGetMethod(key, targetClass, methodName, argTypes);
if (method != null) { methodCache.putIfAbsent(key, method);
methodCache.putIfAbsent(key, method);
} else {
// 对于不存在的 Method只进行一次获取操作主要为了支持 null safe未来需要考虑内存泄漏风险
methodCache.putIfAbsent(key, Void.class);
}
} }
return method instanceof MethodInfo ? (MethodInfo)method : null;
return method;
} }
/**
* 获取 getter 方法
* 使用与 Field 相同的 key避免生成两次 key值
* ---> jfinal 3.5 已将此功能转移至 FieldKit
public static MethodInfo getGetterMethod(Long key, Class<?> targetClass, String methodName) {
Object getterMethod = methodCache.get(key);
if (getterMethod == null) {
getterMethod = doGetMethod(key, targetClass, methodName, NULL_ARG_TYPES);
if (getterMethod != null) {
methodCache.putIfAbsent(key, getterMethod);
} else {
methodCache.putIfAbsent(key, Void.class);
}
}
return getterMethod instanceof MethodInfo ? (MethodInfo)getterMethod : null;
} */
static Class<?>[] getArgTypes(Object[] argValues) { static Class<?>[] getArgTypes(Object[] argValues) {
if (argValues == null || argValues.length == 0) { if (argValues == null || argValues.length == 0) {
return NULL_ARG_TYPES; return NULL_ARG_TYPES;
@ -167,7 +150,9 @@ public class MethodKit {
if (forbiddenClasses.contains(targetClass)) { if (forbiddenClasses.contains(targetClass)) {
throw new RuntimeException("Forbidden class: " + targetClass.getName()); throw new RuntimeException("Forbidden class: " + targetClass.getName());
} }
// 仅开启 forbiddenClasses 检测 // 仅开启 forbiddenClasses 检测
// MethodSharedMethodStaticMethod 已用 MethodKit.isForbiddenMethod(...) 检测
// if (forbiddenMethods.contains(methodName)) { // if (forbiddenMethods.contains(methodName)) {
// throw new RuntimeException("Forbidden method: " + methodName); // throw new RuntimeException("Forbidden method: " + methodName);
// } // }
@ -184,7 +169,8 @@ public class MethodKit {
} }
} }
} }
return null;
return NullMethodInfo.me;
} }
static boolean matchFixedArgTypes(Class<?>[] paraTypes, Class<?>[] argTypes) { static boolean matchFixedArgTypes(Class<?>[] paraTypes, Class<?>[] argTypes) {

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) 2011-2019, 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;
/**
* NullMethodInfo
*
* 1MethodKit.getMethod(...) 消除 instanceof 判断
* 2Method.exec(...) 消除 null 值判断
*/
public class NullMethodInfo extends MethodInfo {
public static final NullMethodInfo me = new NullMethodInfo();
public boolean notNull() {
return false;
}
public Object invoke(Object target, Object... args) throws ReflectiveOperationException {
throw new RuntimeException("The method invoke(Object, Object...) of NullMethodInfo should not be invoked");
}
}

View File

@ -50,6 +50,14 @@ public class StaticMethod extends Expr {
} catch (Exception e) { } catch (Exception e) {
throw new ParseException(e.getMessage(), location, e); throw new ParseException(e.getMessage(), location, e);
} }
if (MethodKit.isForbiddenClass(this.clazz)) {
throw new ParseException("Forbidden class: " + this.clazz.getName(), location);
}
if (MethodKit.isForbiddenMethod(methodName)) {
throw new ParseException("Forbidden method: " + methodName, location);
}
this.methodName = methodName; this.methodName = methodName;
this.exprList = exprList; this.exprList = exprList;
this.location = location; this.location = location;
@ -61,7 +69,7 @@ public class StaticMethod extends Expr {
try { try {
MethodInfo methodInfo = MethodKit.getMethod(clazz, methodName, argValues); MethodInfo methodInfo = MethodKit.getMethod(clazz, methodName, argValues);
if (methodInfo != null) { if (methodInfo.notNull()) {
if (methodInfo.isStatic()) { if (methodInfo.isStatic()) {
return methodInfo.invoke(null, argValues); return methodInfo.invoke(null, argValues);
} else { } else {

View File

@ -16,9 +16,12 @@
package com.jfinal.template.ext.directive; package com.jfinal.template.ext.directive;
import java.io.IOException;
import com.jfinal.template.Directive; import com.jfinal.template.Directive;
import com.jfinal.template.Env; import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.io.Writer; import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope; import com.jfinal.template.stat.Scope;
/** /**
@ -29,45 +32,62 @@ import com.jfinal.template.stat.Scope;
public class EscapeDirective extends Directive { public class EscapeDirective extends Directive {
public void exec(Env env, Scope scope, Writer writer) { public void exec(Env env, Scope scope, Writer writer) {
Object value = exprList.eval(scope); try {
if (value != null) { Object value = exprList.eval(scope);
write(writer, escape(value.toString()));
if (value instanceof String) {
escape((String)value, writer);
} else if (value instanceof Number) {
Class<?> c = value.getClass();
if (c == Integer.class) {
writer.write((Integer)value);
} else if (c == Long.class) {
writer.write((Long)value);
} else if (c == Double.class) {
writer.write((Double)value);
} else if (c == Float.class) {
writer.write((Float)value);
} else if (c == Short.class) {
writer.write((Short)value);
} else {
writer.write(value.toString());
}
} else if (value != null) {
escape(value.toString(), writer);
}
} catch(TemplateException | ParseException e) {
throw e;
} catch(Exception e) {
throw new TemplateException(e.getMessage(), location, e);
} }
} }
// TODO 挪到 StrKit private void escape(String str, Writer w) throws IOException {
private String escape(String str) { for (int i = 0, len = str.length(); i < len; i++) {
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); char cur = str.charAt(i);
switch (cur) { switch (cur) {
case '<': case '<':
ret.append("&lt;"); w.write("&lt;");
break; break;
case '>': case '>':
ret.append("&gt;"); w.write("&gt;");
break; break;
case '"': case '"':
ret.append("&quot;"); w.write("&quot;");
break; break;
case '\'': case '\'':
// ret.append("&apos;"); // IE 不支持 &apos; 考虑 &#39; // w.write("&apos;"); // IE 不支持 &apos; 考虑 &#39;
ret.append("&#39;"); w.write("&#39;");
break; break;
case '&': case '&':
ret.append("&amp;"); w.write("&amp;");
break; break;
default: default:
ret.append(cur); w.write(str, i, 1);
break; break;
} }
} }
return ret.toString();
} }
} }

View File

@ -53,10 +53,9 @@ public class NumberDirective extends Directive {
private Expr valueExpr; private Expr valueExpr;
private Expr patternExpr; private Expr patternExpr;
private int paraNum;
public void setExprList(ExprList exprList) { public void setExprList(ExprList exprList) {
this.paraNum = exprList.length(); int paraNum = exprList.length();
if (paraNum == 0) { if (paraNum == 0) {
throw new ParseException("The parameter of #number directive can not be blank", location); throw new ParseException("The parameter of #number directive can not be blank", location);
} }
@ -64,13 +63,8 @@ public class NumberDirective extends Directive {
throw new ParseException("Wrong number parameter of #number directive, two parameters allowed at most", location); throw new ParseException("Wrong number parameter of #number directive, two parameters allowed at most", location);
} }
if (paraNum == 1) { valueExpr = exprList.getExpr(0);
this.valueExpr = exprList.getExpr(0); patternExpr = (paraNum == 1 ? null : exprList.getExpr(1));
this.patternExpr = null;
} else if (paraNum == 2) {
this.valueExpr = exprList.getExpr(0);
this.patternExpr = exprList.getExpr(1);
}
} }
public void exec(Env env, Scope scope, Writer writer) { public void exec(Env env, Scope scope, Writer writer) {
@ -79,9 +73,9 @@ public class NumberDirective extends Directive {
return ; return ;
} }
if (paraNum == 1) { if (patternExpr == null) {
outputWithoutPattern(writer, value); outputWithoutPattern(writer, value);
} else if (paraNum == 2) { } else {
outputWithPattern(scope, writer, value); outputWithPattern(scope, writer, value);
} }
} }

View File

@ -194,6 +194,14 @@ public class JFinalViewResolver extends AbstractTemplateViewResolver {
engine.setSourceFactory(sourceFactory); engine.setSourceFactory(sourceFactory);
} }
/**
* 设置为 ClassPathSourceFactory 的快捷方法
* ClassPathSourceFactory 将从 CLASSPATH jar 包中读取模板
*/
public void setToClassPathSourceFactory() {
engine.setToClassPathSourceFactory();
}
/** /**
* 设置模板基础路径 * 设置模板基础路径
*/ */

View File

@ -50,15 +50,17 @@ public class ByteWriter extends Writer {
} }
public void write(String str, int offset, int len) throws IOException { public void write(String str, int offset, int len) throws IOException {
while (len > chars.length) { int size, byteLen;
write(str, offset, chars.length); while (len > 0) {
offset += chars.length; size = (len > chars.length ? chars.length : len);
len -= chars.length;
str.getChars(offset, offset + size, chars, 0);
byteLen = encoder.encode(chars, 0, size, bytes);
out.write(bytes, 0, byteLen);
offset += size;
len -= size;
} }
str.getChars(offset, offset + len, chars, 0);
int byteLen = encoder.encode(chars, 0, len, bytes);
out.write(bytes, 0, byteLen);
} }
public void write(String str) throws IOException { public void write(String str) throws IOException {
@ -66,15 +68,17 @@ public class ByteWriter extends Writer {
} }
public void write(StringBuilder stringBuilder, int offset, int len) throws IOException { public void write(StringBuilder stringBuilder, int offset, int len) throws IOException {
while (len > chars.length) { int size, byteLen;
write(stringBuilder, offset, chars.length); while (len > 0) {
offset += chars.length; size = (len > chars.length ? chars.length : len);
len -= chars.length;
stringBuilder.getChars(offset, offset + size, chars, 0);
byteLen = encoder.encode(chars, 0, size, bytes);
out.write(bytes, 0, byteLen);
offset += size;
len -= size;
} }
stringBuilder.getChars(offset, offset + len, chars, 0);
int byteLen = encoder.encode(chars, 0, len, bytes);
out.write(bytes, 0, byteLen);
} }
public void write(StringBuilder stringBuilder) throws IOException { public void write(StringBuilder stringBuilder) throws IOException {

View File

@ -44,14 +44,16 @@ public class CharWriter extends Writer {
} }
public void write(String str, int offset, int len) throws IOException { public void write(String str, int offset, int len) throws IOException {
while (len > chars.length) { int size;
write(str, offset, chars.length); while (len > 0) {
offset += chars.length; size = (len > chars.length ? chars.length : len);
len -= chars.length;
str.getChars(offset, offset + size, chars, 0);
out.write(chars, 0, size);
offset += size;
len -= size;
} }
str.getChars(offset, offset + len, chars, 0);
out.write(chars, 0, len);
} }
public void write(String str) throws IOException { public void write(String str) throws IOException {
@ -59,14 +61,16 @@ public class CharWriter extends Writer {
} }
public void write(StringBuilder stringBuilder, int offset, int len) throws IOException { public void write(StringBuilder stringBuilder, int offset, int len) throws IOException {
while (len > chars.length) { int size;
write(stringBuilder, offset, chars.length); while (len > 0) {
offset += chars.length; size = (len > chars.length ? chars.length : len);
len -= chars.length;
stringBuilder.getChars(offset, offset + size, chars, 0);
out.write(chars, 0, size);
offset += size;
len -= size;
} }
stringBuilder.getChars(offset, offset + len, chars, 0);
out.write(chars, 0, len);
} }
public void write(StringBuilder stringBuilder) throws IOException { public void write(StringBuilder stringBuilder) throws IOException {

View File

@ -25,7 +25,7 @@ import java.util.Map;
*/ */
public class DateFormats { public class DateFormats {
private Map<String, SimpleDateFormat> map = new HashMap<String, SimpleDateFormat>(); private Map<String, SimpleDateFormat> map = new HashMap<String, SimpleDateFormat>(16, 0.25F);
public SimpleDateFormat getDateFormat(String datePattern) { public SimpleDateFormat getDateFormat(String datePattern) {
SimpleDateFormat ret = map.get(datePattern); SimpleDateFormat ret = map.get(datePattern);

View File

@ -16,102 +16,153 @@
package com.jfinal.template.io; package com.jfinal.template.io;
import java.io.IOException;
import java.io.Writer; import java.io.Writer;
/** /**
* FastStringWriter * FastStringWriter
* *
* <pre> * <pre>
* JDK StringWriter 改造而来在其基础之上做了如下改变 * JDK Writer 改造而来在其基础之上做了如下改变
* 1StringBuffer 属性改为 StringBuilder避免了前者的 synchronized 操作 * 1添加 char[] value 直接保存 char
* 2添加了 MAX_SIZE 属性 * 2添加 int len 记录数据长度
* 3去掉了 close() 方法声明中的 throws IOException并添加了代码原先该方法中无任何代码 * 3去掉 synchronized 操作
* 4添加 MAX_BUFFER_SIZE限定 value 被重用的最大长度
* 5去掉了 close() 方法声明中的 throws IOException并添加缓存回收逻辑
* </pre> * </pre>
*/ */
public class FastStringWriter extends Writer { public class FastStringWriter extends Writer {
private StringBuilder buf; private char[] value;
private int len;
public FastStringWriter() { private static int MAX_BUFFER_SIZE = 1024 * 256; // 1024 * 64;
buf = new StringBuilder();
} public static void setMaxBufferSize(int maxBufferSize) {
int min = 256;
public FastStringWriter(int initialSize) { if (maxBufferSize < min) {
if (initialSize < 0) { throw new IllegalArgumentException("maxBufferSize must more than " + min);
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() {
}
static int MAX_SIZE = 1024 * 64;
/**
* StringWriter.close() 改造而来原先该方法中无任何代码 改造如下
* 1去掉 throws IOException
* 2添加 buf 空间释放处理逻辑
* 3添加 buf.setLength(0)以便于配合 ThreadLocal 回收利用
*/
public void close() {
if (buf.length() > MAX_SIZE) {
buf = new StringBuilder(MAX_SIZE / 2); // 释放空间占用过大的 buf
} else {
buf.setLength(0);
} }
} MAX_BUFFER_SIZE = maxBufferSize;
}
@Override
public void close() /* throws IOException */ {
len = 0;
// 释放空间占用过大的缓存
if (value.length > MAX_BUFFER_SIZE) {
value = new char[Math.max(256, MAX_BUFFER_SIZE / 2)];
}
}
public String toString() {
return new String(value, 0, len);
}
public StringBuilder toStringBuilder() {
return new StringBuilder(len + 64).append(value, 0, len);
}
public FastStringWriter(int capacity) {
value = new char[capacity];
}
public FastStringWriter() {
this(128);
}
/**
* 扩容
*/
protected void expandCapacity(int newLen) {
int newCapacity = Math.max(newLen, value.length * 2);
char[] newValue = new char[newCapacity];
// 复制 value 中的值到 newValue
if (len > 0) {
System.arraycopy(value, 0, newValue, 0, len);
}
value = newValue;
}
@Override
public void write(char buffer[], int offset, int len) throws IOException {
int newLen = this.len + len;
if (newLen > value.length) {
expandCapacity(newLen);
}
System.arraycopy(buffer, offset, value, this.len, len);
this.len = newLen;
}
@Override
public void write(String str, int offset, int len) throws IOException {
int newLen = this.len + len;
if (newLen > value.length) {
expandCapacity(newLen);
}
str.getChars(offset, (offset + len), value, this.len);
this.len = newLen;
}
@Override
public void write(int c) throws IOException {
char[] buffer = {(char)c};
write(buffer, 0, 1);
}
@Override
public void write(char buffer[]) throws IOException {
write(buffer, 0, buffer.length);
}
@Override
public void write(String str) throws IOException {
write(str, 0, str.length());
}
@Override
public Writer append(CharSequence csq) throws IOException {
if (csq instanceof String) {
String str = (String)csq;
write(str, 0, str.length());
return this;
}
if (csq == null)
write("null");
else
write(csq.toString());
return this;
}
@Override
public Writer append(CharSequence csq, int start, int end) throws IOException {
if (csq instanceof String) {
String str = (String)csq;
write(str, start, (end - start));
return this;
}
CharSequence cs = (csq == null ? "null" : csq);
write(cs.subSequence(start, end).toString());
return this;
}
@Override
public Writer append(char c) throws IOException {
char[] buffer = {c};
write(buffer, 0, 1);
return this;
}
@Override
public void flush() throws IOException {
}
} }

View File

@ -0,0 +1,38 @@
/**
* Copyright (c) 2011-2019, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.template.io;
/**
* JdkEncoderFactory
*
* 支持 utf8mb4支持 emoji 表情字符支持各种罕见字符编码
*
* <pre>
* 配置方法
* engine.setToJdkEncoderFactory();
* </pre>
*/
public class JdkEncoderFactory extends EncoderFactory {
@Override
public Encoder getEncoder() {
return new JdkEncoder(charset);
}
}

View File

@ -16,7 +16,7 @@
package com.jfinal.template.io; package com.jfinal.template.io;
import java.nio.charset.MalformedInputException; // import java.nio.charset.MalformedInputException;
/** /**
* Utf8Encoder * Utf8Encoder
@ -62,12 +62,16 @@ public class Utf8Encoder extends Encoder {
if (Character.isLowSurrogate(d)) { if (Character.isLowSurrogate(d)) {
uc = Character.toCodePoint(c, d); uc = Character.toCodePoint(c, d);
} else { } else {
throw new RuntimeException("encode UTF8 error", new MalformedInputException(1)); // throw new RuntimeException("encode UTF8 error", new MalformedInputException(1));
bytes[dp++] = (byte) '?';
continue;
} }
} }
} else { } else {
if (Character.isLowSurrogate(c)) { if (Character.isLowSurrogate(c)) {
throw new RuntimeException("encode UTF8 error", new MalformedInputException(1)); // throw new RuntimeException("encode UTF8 error", new MalformedInputException(1));
bytes[dp++] = (byte) '?';
continue;
} else { } else {
uc = c; uc = c;
} }

View File

@ -18,6 +18,7 @@ package com.jfinal.template.stat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set;
/** /**
* DKFF(Dynamic Key Feature Forward) Lexer * DKFF(Dynamic Key Feature Forward) Lexer
@ -35,10 +36,14 @@ class Lexer {
int forwardRow = 1; int forwardRow = 1;
TextToken previousTextToken = null; TextToken previousTextToken = null;
List<Token> tokens = new ArrayList<Token>();
String fileName; String fileName;
Set<String> keepLineBlankDirectives;
public Lexer(StringBuilder content, String fileName) { List<Token> tokens = new ArrayList<Token>();
public Lexer(StringBuilder content, String fileName, Set<String> keepLineBlankDirectives) {
this.keepLineBlankDirectives = keepLineBlankDirectives;
int len = content.length(); int len = content.length();
buf = new char[len + 1]; buf = new char[len + 1];
content.getChars(0, content.length(), buf, 0); content.getChars(0, content.length(), buf, 0);
@ -110,7 +115,7 @@ class Lexer {
para = scanPara(""); para = scanPara("");
idToken = new Token(Symbol.OUTPUT, beginRow); idToken = new Token(Symbol.OUTPUT, beginRow);
paraToken = new ParaToken(para, beginRow); paraToken = new ParaToken(para, beginRow);
return addOutputToken(idToken, paraToken); return addIdParaToken(idToken, paraToken);
} }
if (CharTable.isLetter(peek())) { // # id if (CharTable.isLetter(peek())) { // # id
state = 10; state = 10;
@ -472,31 +477,6 @@ class Lexer {
} }
} }
// 输出指令不对前后空白与换行进行任何处理直接调用 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;
}
}
/** /**
* 带参指令处于独立行时删除前后空白字符并且再删除一个后续的换行符 * 带参指令处于独立行时删除前后空白字符并且再删除一个后续的换行符
* 处于独立行是指向前看无有用内容在前面情况成立的基础之上 * 处于独立行是指向前看无有用内容在前面情况成立的基础之上
@ -509,32 +489,68 @@ class Lexer {
tokens.add(idToken); tokens.add(idToken);
tokens.add(paraToken); tokens.add(paraToken);
skipFollowingComment();
// 保留指令所在行空白字符
// #define xxx() 模板函数名#@xxx() 模板函数名可以与指令同名需要排除掉这三种 Symbol
if (keepLineBlankDirectives.contains(idToken.value())
&& idToken.symbol != Symbol.DEFINE
&& idToken.symbol != Symbol.CALL
&& idToken.symbol != Symbol.CALL_IF_DEFINED
) {
prepareNextScan(0);
} else {
trimLineBlank();
}
previousTextToken = null;
return true;
}
// #set 这类指令处在独立一行时需要删除当前行的前后空白字符以及行尾字符 '\n'
void trimLineBlank() {
// if (lookForwardLineFeed() && (deletePreviousTextTokenBlankTails() || lexemeBegin == 0)) { // if (lookForwardLineFeed() && (deletePreviousTextTokenBlankTails() || lexemeBegin == 0)) {
if (lookForwardLineFeedAndEof() && deletePreviousTextTokenBlankTails()) { if (lookForwardLineFeedAndEof() && deletePreviousTextTokenBlankTails()) {
prepareNextScan(peek() != EOF ? 1 : 0); prepareNextScan(peek() != EOF ? 1 : 0);
} else { } else {
prepareNextScan(0); prepareNextScan(0);
} }
previousTextToken = null;
return true;
} }
// 处理前后空白的逻辑与 addIdParaToken() 基本一样仅仅多了一个对于紧随空白的 next() 操作 // 无参指令无条件调用 trimLineBlank()
boolean addNoParaToken(Token noParaToken) { boolean addNoParaToken(Token noParaToken) {
tokens.add(noParaToken); tokens.add(noParaToken);
skipFollowingComment();
if (CharTable.isBlank(peek())) { if (CharTable.isBlank(peek())) {
next(); // 无参指令之后紧随的一个空白字符仅为分隔符不参与后续扫描 next(); // 无参指令之后紧随的一个空白字符仅为分隔符不参与后续扫描
} }
if (lookForwardLineFeedAndEof() && deletePreviousTextTokenBlankTails()) { trimLineBlank();
prepareNextScan(peek() != EOF ? 1 : 0);
} else {
prepareNextScan(0);
}
previousTextToken = null; previousTextToken = null;
return true; return true;
} }
// 向前看后续是否跟随的是空白 + 换行或者是空白 + EOF是则表示当前指令后续没有其它有用内容
boolean lookForwardLineFeedAndEof() {
int fp = forward;
for (char c=buf[fp]; true; c=buf[++fp]) {
if (CharTable.isBlank(c)) {
continue ;
}
if (c == '\n' || c == EOF) {
forward = fp;
return true;
}
return false;
}
}
/** /**
* 1当前指令前方仍然是指令 (previousTextToken null)直接返回 true * 1当前指令前方仍然是指令 (previousTextToken null)直接返回 true
* 2当前指令前方为 TextToken 时的处理逻辑与返回值完全依赖于 TextToken.deleteBlankTails() * 2当前指令前方为 TextToken 时的处理逻辑与返回值完全依赖于 TextToken.deleteBlankTails()
@ -543,6 +559,54 @@ class Lexer {
// return previousTextToken != null ? previousTextToken.deleteBlankTails() : false; // return previousTextToken != null ? previousTextToken.deleteBlankTails() : false;
return previousTextToken == null || previousTextToken.deleteBlankTails(); return previousTextToken == null || previousTextToken.deleteBlankTails();
} }
/**
* 跳过指令后方跟随的注释以便正确处理各类换行逻辑
*/
void skipFollowingComment() {
int fp = forward;
for (char c=buf[fp]; true; c=buf[++fp]) {
if (CharTable.isBlank(c)) {
continue ;
}
// 勿使用 next()
if (c == '#') {
if (buf[fp + 1] == '#' && buf[fp + 2] == '#') {
forward = fp;
skipFollowingSingleLineComment();
} else if (buf[fp + 1] == '-' && buf[fp + 2] == '-') {
forward = fp;
skipFollowingMultiLineComment();
}
}
return ;
}
}
void skipFollowingSingleLineComment() {
forward = forward + 3;
for (char c=peek(); true; c=next()) {
if (c == '\n' || c == EOF) {
break ;
}
}
}
void skipFollowingMultiLineComment() {
forward = forward + 3;
for (char c=peek(); true; c=next()) {
if (c == '-' && buf[forward + 1] == '-' && buf[forward + 2] == '#') {
forward = forward + 3;
break ;
}
if (c == EOF) {
throw new ParseException("The multiline comment start block \"#--\" can not match the end block: \"--#\"", new Location(fileName, beginRow));
}
}
}
} }

View File

@ -71,7 +71,7 @@ public class Parser {
} }
public StatList parse() { public StatList parse() {
tokenList = new Lexer(content, fileName).scan(); tokenList = new Lexer(content, fileName, env.getEngineConfig().getKeepLineBlankDirectives()).scan();
tokenList.add(EOF); tokenList.add(EOF);
StatList statList = statList(); StatList statList = statList();
if (peek() != EOF) { if (peek() != EOF) {
@ -207,11 +207,11 @@ public class Parser {
matchEnd(name); matchEnd(name);
} }
return ret; return ret;
case EOF:
case PARA: case PARA:
case ELSEIF: case ELSEIF:
case ELSE: case ELSE:
case END: case END:
case EOF:
case CASE: case CASE:
case DEFAULT: case DEFAULT:
return null; return null;

View File

@ -78,6 +78,7 @@ public class Scope {
if (cur.data == null) { // 支持顶层 data null if (cur.data == null) { // 支持顶层 data null
cur.data = new HashMap(); cur.data = new HashMap();
} }
cur.data.put(key, value); cur.data.put(key, value);
return ; return ;
} }
@ -89,7 +90,8 @@ public class Scope {
* 自内向外在作用域栈中查找变量返回最先找到的变量 * 自内向外在作用域栈中查找变量返回最先找到的变量
*/ */
public Object get(Object key) { public Object get(Object key) {
for (Scope cur=this; cur!=null; cur=cur.parent) { Scope cur = this;
do {
// if (cur.data != null && cur.data.containsKey(key)) { // if (cur.data != null && cur.data.containsKey(key)) {
// return cur.data.get(key); // return cur.data.get(key);
// } // }
@ -104,7 +106,10 @@ public class Scope {
return null; return null;
} }
} }
}
cur = cur.parent;
} while (cur != null);
// return null; // return null;
return sharedObjectMap != null ? sharedObjectMap.get(key) : null; return sharedObjectMap != null ? sharedObjectMap.get(key) : null;
} }
@ -155,6 +160,10 @@ public class Scope {
public void setGlobal(Object key, Object value) { public void setGlobal(Object key, Object value) {
for (Scope cur=this; true; cur=cur.parent) { for (Scope cur=this; true; cur=cur.parent) {
if (cur.parent == null) { if (cur.parent == null) {
if (cur.data == null) {
cur.data = new HashMap();
}
cur.data.put(key, value); cur.data.put(key, value);
return ; return ;
} }
@ -168,7 +177,7 @@ public class Scope {
public Object getGlobal(Object key) { public Object getGlobal(Object key) {
for (Scope cur=this; true; cur=cur.parent) { for (Scope cur=this; true; cur=cur.parent) {
if (cur.parent == null) { if (cur.parent == null) {
return cur.data.get(key); return cur.data != null ? cur.data.get(key) : null;
} }
} }
} }
@ -180,7 +189,10 @@ public class Scope {
public void removeGlobal(Object key) { public void removeGlobal(Object key) {
for (Scope cur=this; true; cur=cur.parent) { for (Scope cur=this; true; cur=cur.parent) {
if (cur.parent == null) { if (cur.parent == null) {
cur.data.remove(key); if (cur.data != null) {
cur.data.remove(key);
}
return ; return ;
} }
} }

View File

@ -63,6 +63,9 @@ class TextToken extends Token {
} }
// 两个指令之间全是空白字符 设置其长度为 0 Parser 过滤内容为空的 Text 节点做准备 // 两个指令之间全是空白字符 设置其长度为 0 Parser 过滤内容为空的 Text 节点做准备
// 典型测试用例两个带有前导空格并且都在独立一行的 #set(...) 指令前一个 #set 指令
// 虽然是 '\n' 结尾但已在 Lexer 中被 prepareNextScan(...) 删掉
// 另一典型用例#date() #date()可通过配置 keepLineBlank true 保留指令间的空白字符
text.setLength(0); text.setLength(0);
return true; // 当两指令之间全为空白字符时告知调用方需要吃掉行尾的 '\n' return true; // 当两指令之间全为空白字符时告知调用方需要吃掉行尾的 '\n'
} }

View File

@ -66,8 +66,6 @@ public class Output extends Stat {
} else { } else {
writer.write(value.toString()); writer.write(value.toString());
} }
} else if (value instanceof Boolean) {
writer.write((Boolean)value);
} else if (value != null) { } else if (value != null) {
writer.write(value.toString()); writer.write(value.toString());
} }

View File

@ -20,6 +20,7 @@ import com.jfinal.template.Env;
import com.jfinal.template.expr.ast.Assign; import com.jfinal.template.expr.ast.Assign;
import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.expr.ast.IncDec;
import com.jfinal.template.io.Writer; import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Location; import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException; import com.jfinal.template.stat.ParseException;
@ -44,10 +45,11 @@ public class Set extends Stat {
} }
for (Expr expr : exprList.getExprArray()) { for (Expr expr : exprList.getExprArray()) {
if ( !(expr instanceof Assign) ) { if ( !(expr instanceof Assign || expr instanceof IncDec) ) {
throw new ParseException("#set directive only supports assignment expressions", location); throw new ParseException("#set directive only supports assignment expressions", location);
} }
} }
this.expr = exprList.getActualExpr(); this.expr = exprList.getActualExpr();
} }

View File

@ -20,6 +20,7 @@ import com.jfinal.template.Env;
import com.jfinal.template.expr.ast.Assign; import com.jfinal.template.expr.ast.Assign;
import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.expr.ast.IncDec;
import com.jfinal.template.io.Writer; import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Ctrl; import com.jfinal.template.stat.Ctrl;
import com.jfinal.template.stat.Location; import com.jfinal.template.stat.Location;
@ -41,10 +42,11 @@ public class SetGlobal extends Stat {
} }
for (Expr expr : exprList.getExprArray()) { for (Expr expr : exprList.getExprArray()) {
if ( !(expr instanceof Assign) ) { if ( !(expr instanceof Assign || expr instanceof IncDec) ) {
throw new ParseException("#setGlobal directive only supports assignment expressions", location); throw new ParseException("#setGlobal directive only supports assignment expressions", location);
} }
} }
this.expr = exprList.getActualExpr(); this.expr = exprList.getActualExpr();
} }

View File

@ -20,6 +20,7 @@ import com.jfinal.template.Env;
import com.jfinal.template.expr.ast.Assign; import com.jfinal.template.expr.ast.Assign;
import com.jfinal.template.expr.ast.Expr; import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList; import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.expr.ast.IncDec;
import com.jfinal.template.io.Writer; import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Ctrl; import com.jfinal.template.stat.Ctrl;
import com.jfinal.template.stat.Location; import com.jfinal.template.stat.Location;
@ -42,10 +43,11 @@ public class SetLocal extends Stat {
} }
for (Expr expr : exprList.getExprArray()) { for (Expr expr : exprList.getExprArray()) {
if ( !(expr instanceof Assign) ) { if ( !(expr instanceof Assign || expr instanceof IncDec) ) {
throw new ParseException("#setLocal directive only supports assignment expressions", location); throw new ParseException("#setLocal directive only supports assignment expressions", location);
} }
} }
this.expr = exprList.getActualExpr(); this.expr = exprList.getActualExpr();
} }

View File

@ -0,0 +1,8 @@
#tpl("findGirl")
select * from girl where age > ? and age < ? and weight < 50
#end
#tpl("findGirl2")
select * from girl where age > ? and age < ? and weight < 50
#end

View File

@ -0,0 +1,28 @@
package com.jfinal.template;
import com.jfinal.kit.Kv;
import com.jfinal.kit.TplKit;
import org.junit.Test;
import java.io.File;
public class TplKitTest {
@Test
public void run() {
TplKit tplKit = TplKit.use("", true);
tplKit.addTplTemplate(new File("E:\\wk\\HaoGamePlatfProject\\conf"), x -> x.getName().endsWith(".tpl"));
tplKit.parseTplTemplate();
TplKit tplKit2 = TplKit.use("", true);
String findGirl = tplKit2.getTpl("abx", Kv.by("a", 12321));
System.out.println(findGirl);
}
}