Compare commits

...

90 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
James
34c1a9e53a [maven-release-plugin] prepare release enjoy-4.0 2019-05-21 22:57:51 +08:00
James
f6a855b6bf enjoy 4.0 release ^_^ 2019-05-21 22:52:26 +08:00
James
4377d19e2f [maven-release-plugin] prepare for next development iteration 2019-04-07 22:30:17 +08:00
James
820f2806ec [maven-release-plugin] prepare release enjoy-3.8 2019-04-07 22:30:15 +08:00
James
808bdf6079 enjoy 3.8 2019-04-07 22:23:49 +08:00
James
eac1d8d055 enjoy 3.8 2019-04-07 21:45:28 +08:00
James
d5a88b8be4 enjoy 3.8 2019-04-07 21:36:00 +08:00
James
fd5d554171 enjoy 3.8 2019-04-07 21:34:21 +08:00
James
6d18be3df8 [maven-release-plugin] prepare for next development iteration 2019-03-19 16:31:01 +08:00
James
869824e2bb [maven-release-plugin] prepare release enjoy-3.7 2019-03-19 16:30:57 +08:00
James
84573be584 enjoy 3.7 release ^_^ 2019-03-19 16:25:33 +08:00
James
3cc94a5b32 enjoy 3.7 release 2019-03-19 16:23:21 +08:00
James
3a4f4f4495 enjoy 3.7 2019-02-25 18:44:34 +08:00
James
d250b431a4 jfinal enjoy 3.6 release ^_^ 2019-01-31 16:45:57 +08:00
James
5e133e7de5 jfinal enjoy 3.6 release ^_^ 2019-01-31 16:45:09 +08:00
James
ef39843a25 jfinal enjoy 3.6 release ^_^ 2019-01-31 16:43:49 +08:00
James
0e5f3b7249 [maven-release-plugin] prepare for next development iteration 2019-01-31 00:15:29 +08:00
James
6f5cd47376 [maven-release-plugin] prepare release enjoy-3.6 2019-01-31 00:15:25 +08:00
James
b23a1a9133 enjoy 3.6 release ^_^ 2019-01-30 21:09:51 +08:00
James
4c63d00157 enjoy 3.6 release ^_^ 2019-01-30 21:09:07 +08:00
James
972c7e7673 enjoy 3.6 2019-01-01 21:17:38 +08:00
James
6d7d0af2b2 [maven-release-plugin] prepare for next development iteration 2018-10-08 17:42:32 +08:00
James
6bf9ae3f59 [maven-release-plugin] prepare release enjoy-3.5 2018-10-08 17:42:28 +08:00
James
d46ca53a34 jfinal enjoy 3.5 2018-10-08 17:34:53 +08:00
James
985b02177e jfinal enjoy 3.5 2018-10-06 17:17:13 +08:00
James
b9cc8a58e0 jfinal enjoy 3.5 2018-10-04 21:47:18 +08:00
James
31ca22d21d jfinal enjoy 3.5 2018-10-04 21:44:17 +08:00
James
13f2d302c3 jfinal enjoy 3.5 2018-10-04 21:38:21 +08:00
James
49d53e9f55 enjoy 3.5 2018-08-22 23:43:31 +08:00
James
8d88d0bba4 enjoy 3.5 2018-08-14 23:11:21 +08:00
James
462c70b692 enjoy 3.5 2018-08-12 12:16:41 +08:00
James
1ce7068072 enjoy 3.5 2018-08-12 12:15:01 +08:00
James
3e89651aa4 [maven-release-plugin] prepare for next development iteration 2018-04-27 12:18:46 +08:00
James
1aef12eb94 [maven-release-plugin] prepare release enjoy-3.4 2018-04-27 12:18:43 +08:00
James
6f615bc1a8 enjoy 3.4 release ^_^ 2018-04-25 22:10:00 +08:00
James
0c3e7790b7 enjoy 3.4 release ^_^ 2018-04-25 22:09:05 +08:00
James
a1e88a4a4c enjoy 3.4 2018-04-02 19:14:24 +08:00
James
d12d3d8abb jfinal 3.4 2018-04-02 14:15:59 +08:00
James
ef7b0da917 enjoy 3.4 2018-04-01 16:29:13 +08:00
James
2ed806c296 enjoy 3.4 2018-03-30 17:06:02 +08:00
James
998df7b907 enjoy 3.4 2018-03-30 02:51:56 +08:00
James
3e93fc970e jfinal enjoy 3.4 2018-03-02 23:41:05 +08:00
James
010a7f3d8a [maven-release-plugin] prepare for next development iteration 2017-12-25 11:47:53 +08:00
James
99f024f546 [maven-release-plugin] prepare release enjoy-3.4 2017-12-25 11:47:51 +08:00
James
ca3361703e enjoy 3.4 2017-12-24 23:37:35 +08:00
James
1840d49e0a [maven-release-plugin] prepare for next development iteration 2017-12-24 22:46:28 +08:00
James
70d1e5a9eb [maven-release-plugin] prepare release enjoy-3.4 2017-12-24 22:46:25 +08:00
James
e0abc218b1 Map 定义初始化表达式,添加支持 key 可以为 int、long、float、double、true、false、null 常量 2017-12-10 19:40:40 +08:00
James
bd1949fc56 [maven-release-plugin] prepare for next development iteration 2017-11-21 23:14:12 +08:00
James
7d8185e9db [maven-release-plugin] prepare release enjoy-3.3 2017-11-21 23:14:05 +08:00
James
07abfad6cc enjoy 3.3 release ^_^ 2017-11-21 22:52:21 +08:00
James
61aa1d2082 enjoy 3.3 release ^_^ 2017-11-21 22:43:34 +08:00
James
28eb105ffa add license apache 2.0 2017-10-28 16:06:10 +08:00
James
5585470ded enjoy 3.3 2017-08-12 12:41:05 +08:00
James
59b23288cb enjoy 3.3 2017-08-12 12:38:58 +08:00
James
68894ea84a 添加 getId() getIndex() getRight() 2017-08-02 15:35:37 +08:00
James
f951d5c793 [maven-release-plugin] prepare for next development iteration 2017-07-31 23:29:56 +08:00
147 changed files with 7420 additions and 1315 deletions

4
.gitignore vendored
View File

@ -50,3 +50,7 @@ integration-repo
a_little_config_pro.txt
dev_plan.txt

194
LICENSE Normal file
View File

@ -0,0 +1,194 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "{}" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright 2018 JFinal Enjoy
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.

139
README.md
View File

@ -1,42 +1,97 @@
### Enjoy
Enjoy 是基于 Java 语言的极轻量极魔板引擎。极轻量级仅 171K 并且不依赖任何第三方。极简设计仅 if、for、set、define、include、render 六个核心指令,让学习成本低到极致。独创 DKFF(Dynamic Key Feature Forward) 词法分析算法与 DLRD (Double Layer Recursive Descent)语法分析算法,避免使用 javacc、antlr、jflex 生成器,令代码量少到极致。
#### Enjoy 主要特点
- 消灭传统模板引擎中大量繁杂概念,仅六个核心指令,学习成本极低
- 独创 DKFF 词法分析算法与 DLRD 语法分析算法避免使用javacc、antlr
- 功能强大,极为简单覆盖掉 freemarker、velocity 的核心功能
- 扩展性强,支持多种扩展方式,且是唯一支持指令级扩展的模板引擎
- 与 java 打通式设计,在模板中与 java 交互极为方便
- 贴近 java 使用直觉,为 java 开发者量身打造
- 回归模板引擎渲染 View 数据的本质,采用指令式设计,避免 view 层表达复杂逻辑
- 体积小,仅 171K且不依赖于任何第三方
#### 简单示例:
**1. 在 spring 中的配置**
```java
<bean id="viewResolver" class="com.jfinal.template.ext.spring.JFinalViewResolver">
<!-- 是否热加载模板文件 -->
<property name="devMode" value="true" />
<!-- 配置shared function多文件用逗号分隔 -->
<property name="sharedFunction" value="/view/_layout.html, /view/_paginate.html" />
<!-- 是否支持以 #(session.value) 的方式访问 session -->
<property name="sessionInView" value="true" />
<property name="prefix" value="/view/" />
<property name="suffix" value=".html" />
<property name="order" value="1" />
<property name="contentType" value="text/html; charset=utf-8" />
</bean>
```
**2.详细使用方法见 jfinal 手册**
read me 正在补充,详细使用文档请下载 jfinal.com 官网的 jfinal 手册[http://www.jfinal.com](http://www.jfinal.com)
**JFinal 官方网站:[http://www.jfinal.com](http://www.jfinal.com)**
### Enjoy
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 主要特点
- 消灭传统模板引擎中大量繁杂概念,仅七个核心指令,学习成本极低
- 独创 DKFF 词法分析算法与 DLRD 语法分析算法,避免使用 javacc、antlr
- 与 java 打通式设计,在模板中与 java 交互极为方便
- 贴近 java 使用直觉,为 java 开发者量身打造
- 功能强大,极为简单覆盖掉 freemarker、velocity 的核心功能
- 扩展性强,支持多种扩展方式,且是唯一支持指令级扩展的模板引擎
- 回归模板引擎渲染 View 数据的本质,采用指令式设计,避免 view 层表达复杂逻辑
- 体积小,仅 227 KB且不依赖于任何第三方
#### 简单示例:
**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
<bean id="viewResolver" class="com.jfinal.template.ext.spring.JFinalViewResolver">
<!-- 是否热加载模板文件 -->
<property name="devMode" value="true" />
<!-- 配置shared function多文件用逗号分隔 -->
<property name="sharedFunction" value="/view/_layout.html, /view/_paginate.html" />
<!-- 是否支持以 #(session.value) 的方式访问 session -->
<property name="sessionInView" value="true" />
<property name="prefix" value="/view/" />
<property name="suffix" value=".html" />
<property name="order" value="1" />
<property name="contentType" value="text/html; charset=utf-8" />
</bean>
```
**3.详细使用方法见官方文档**
read me 正在补充,详细使用文档见官网:[https://www.jfinal.com/doc/6-1](https://www.jfinal.com/doc/6-1)
**JFinal Enjoy 官方文档:[https://www.jfinal.com/doc/6-1](https://www.jfinal.com/doc/6-1)**

277
pom.xml
View File

@ -1,130 +1,147 @@
<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>
<groupId>com.jfinal</groupId>
<artifactId>enjoy</artifactId>
<packaging>jar</packaging>
<name>enjoy</name>
<version>3.2</version>
<url>http://www.jfinal.com</url>
<description>Enjoy is a simple, light, rapid, independent, extensible Java Template Engine.</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<issueManagement>
<system>Git Issue</system>
<url>http://git.oschina.net/jfinal/enjoy/issues</url>
</issueManagement>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<developers>
<developer>
<id>jfinal</id>
<name>James</name>
<email>jfinal@126.com</email>
<url>http://jfinal.com/user/1</url>
</developer>
</developers>
<scm>
<connection>scm:git:git@git.oschina.net:jfinal/enjoy.git</connection>
<developerConnection>scm:git:git@git.oschina.net:jfinal/enjoy.git</developerConnection>
<url>git@git.oschina.net:jfinal/enjoy.git</url>
</scm>
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>7</version>
</parent>
<repositories>
</repositories>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.8.RELEASE</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.3</version>
<configuration>
<!-- 解决 java8 发布到 maven 异常 -->
<additionalparam>-Xdoclint:none</additionalparam>
<encoding>UTF-8</encoding>
<outputDirectory>${basedir}</outputDirectory>
<reportOutputDirectory>${basedir}</reportOutputDirectory>
</configuration>
</plugin>
<!-- 安装源码到本地仓库 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.1.2</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
<configuration>
<skip>false</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jfinal</groupId>
<artifactId>enjoy</artifactId>
<version>4.8.dev</version>
<packaging>jar</packaging>
<name>enjoy</name>
<description>Enjoy is a simple, light, rapid, independent, extensible Java Template Engine.</description>
<url>http://www.jfinal.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<issueManagement>
<system>Git Issue</system>
<url>https://gitee.com/jfinal/enjoy/issues</url>
</issueManagement>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<!--<developers>
<developer>
<id>jfinal</id>
<name>James</name>
<email>jfinal@126.com</email>
<url>http://jfinal.com/user/1</url>
</developer>
</developers>
<scm>
<connection>scm:git:git@gitee.com:jfinal/enjoy.git</connection>
<developerConnection>scm:git:git@gitee.com:jfinal/enjoy.git</developerConnection>
<url>git@gitee.com:jfinal/enjoy.git</url>
</scm>-->
<distributionManagement>
<repository>
<id>mvn-release</id>
<name>mvn-release</name>
<url>https://nexus.1216.top/repository/maven-releases/</url>
</repository>
</distributionManagement>
<repositories>
</repositories>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.8.RELEASE</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<defaultGoal>compile</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.4</version>
<configuration>
<!-- 解决 java8 发布到 maven 异常 -->
<additionalparam>-Xdoclint:none</additionalparam>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 安装源码到本地仓库 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.1.2</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<!--<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>-->
</plugins>
</build>
</project>

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -16,12 +16,12 @@
package com.jfinal.kit;
import java.io.Writer;
import java.util.Map;
import com.jfinal.template.Directive;
import com.jfinal.template.Engine;
import com.jfinal.template.Env;
import com.jfinal.template.Template;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope;
/**
@ -42,10 +42,10 @@ public class ElKit {
private static final String RETURN_VALUE_KEY = "_RETURN_VALUE_";
static {
engine.addDirective("eval", new InnerEvalDirective());
engine.addDirective("eval", InnerEvalDirective.class);
}
public Engine getEngine() {
public static Engine getEngine() {
return engine;
}
@ -57,7 +57,7 @@ public class ElKit {
public static <T> T eval(String expr, Map<?, ?> data) {
String stringTemplate = "#eval(" + expr + ")";
Template template = engine.getTemplateByString(stringTemplate);
template.render(data, null);
template.render(data, (java.io.Writer)null);
return (T)data.get(RETURN_VALUE_KEY);
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -20,10 +20,22 @@ import java.security.MessageDigest;
public class HashKit {
public static final long FNV_OFFSET_BASIS_64 = 0xcbf29ce484222325L;
public static final long FNV_PRIME_64 = 0x100000001b3L;
private static final java.security.SecureRandom random = new java.security.SecureRandom();
private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
private static final char[] CHAR_ARRAY = "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
public static long fnv1a64(String key) {
long hash = FNV_OFFSET_BASIS_64;
for(int i=0, size=key.length(); i<size; i++) {
hash ^= key.charAt(i);
hash *= FNV_PRIME_64;
}
return hash;
}
public static String md5(String srcStr){
return hash("MD5", srcStr);
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -30,11 +30,6 @@ import java.util.Map;
@SuppressWarnings({"serial", "rawtypes", "unchecked"})
public class Kv extends HashMap {
@Deprecated
private static final String STATE_OK = "isOk";
@Deprecated
private static final String STATE_FAIL = "isFail";
public Kv() {
}
@ -46,73 +41,25 @@ public class Kv extends HashMap {
return new Kv();
}
@Deprecated
public static Kv ok() {
return new Kv().setOk();
}
@Deprecated
public static Kv ok(Object key, Object value) {
return ok().set(key, value);
}
@Deprecated
public static Kv fail() {
return new Kv().setFail();
}
@Deprecated
public static Kv fail(Object key, Object value) {
return fail().set(key, value);
}
@Deprecated
public Kv setOk() {
super.put(STATE_OK, Boolean.TRUE);
super.put(STATE_FAIL, Boolean.FALSE);
return this;
}
@Deprecated
public Kv setFail() {
super.put(STATE_FAIL, Boolean.TRUE);
super.put(STATE_OK, Boolean.FALSE);
return this;
}
@Deprecated
public boolean isOk() {
Boolean isOk = (Boolean)get(STATE_OK);
if (isOk != null) {
return isOk;
}
Boolean isFail = (Boolean)get(STATE_FAIL);
if (isFail != null) {
return !isFail;
}
throw new IllegalStateException("调用 isOk() 之前,必须先调用 ok()、fail() 或者 setOk()、setFail() 方法");
}
@Deprecated
public boolean isFail() {
Boolean isFail = (Boolean)get(STATE_FAIL);
if (isFail != null) {
return isFail;
}
Boolean isOk = (Boolean)get(STATE_OK);
if (isOk != null) {
return !isOk;
}
throw new IllegalStateException("调用 isFail() 之前,必须先调用 ok()、fail() 或者 setOk()、setFail() 方法");
}
public Kv set(Object key, Object value) {
super.put(key, value);
return this;
}
public Kv setIfNotBlank(Object key, String value) {
if (StrKit.notBlank(value)) {
set(key, value);
}
return this;
}
public Kv setIfNotNull(Object key, Object value) {
if (value != null) {
set(key, value);
}
return this;
}
public Kv set(Map map) {
super.putAll(map);
return this;

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -16,6 +16,9 @@
package com.jfinal.kit;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
/**
* 反射工具类
*/
@ -24,11 +27,50 @@ public class ReflectKit {
public static Object newInstance(Class<?> clazz) {
try {
return clazz.newInstance();
} catch (Exception e) {
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public static String getMethodSignature(Method method) {
StringBuilder ret = new StringBuilder()
.append(method.getDeclaringClass().getName())
.append(".")
.append(method.getName())
.append("(");
int index = 0;
Parameter[] paras = method.getParameters();
for (Parameter p : paras) {
if (index++ > 0) {
ret.append(", ");
}
ret.append(p.getParameterizedType().getTypeName());
}
return ret.append(")").toString();
}
/*
public static String getMethodSignature(Method method) {
StringBuilder ret = new StringBuilder()
.append(method.getDeclaringClass().getName())
.append(".")
.append(method.getName())
.append("(");
int index = 0;
java.lang.reflect.Type[] paraTypes = method.getGenericParameterTypes();
for (java.lang.reflect.Type type : paraTypes) {
if (index++ > 0) {
ret.append(", ");
}
ret.append(type.getTypeName());
}
return ret.append(")").toString();
}*/
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -102,6 +102,10 @@ public class StrKit {
return true;
}
public static String defaultIfBlank(String str, String defaultValue) {
return isBlank(str) ? defaultValue : str;
}
public static String toCamelCase(String stringWithUnderline) {
if (stringWithUnderline.indexOf('_') == -1) {
return stringWithUnderline;
@ -145,6 +149,17 @@ public class StrKit {
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) {
byte[] aBytes = (a != null ? a.getBytes() : null);
byte[] bBytes = (b != null ? b.getBytes() : null);

View File

@ -0,0 +1,92 @@
/**
* 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;
import java.util.HashMap;
import java.util.Map;
/**
* SyncWriteMap 同步写 HashMap
* 创建原因是 HashMap扩容时遇到并发修改可能造成 100% CPU 占用
*
* SyncWriteMap 拥有 HashMap 的性能但不保障并发访问的线程安全
* 只用于读多写少且不用保障线程安全的场景
*
* 例如 MethodKit 中用于缓存 MethodInfo cache被写入的数据
* 不用保障是单例读取之后会做 null 值判断
*
* ActionMapping 中的 HashMap 是系统启动时在独立线程内初始化的
* 不存在并发写只存在并发读的情况所以仍然可以使用 HashMap
*/
public class SyncWriteMap<K, V> extends HashMap<K, V> {
private static final long serialVersionUID = -7287230891751869148L;
public SyncWriteMap() {
}
public SyncWriteMap(int initialCapacity) {
super(initialCapacity);
}
public SyncWriteMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
}
public SyncWriteMap(Map<? extends K, ? extends V> m) {
super(m);
}
@Override
public V put(K key, V value) {
synchronized (this) {
return super.put(key, value);
}
}
@Override
public V putIfAbsent(K key, V value) {
synchronized (this) {
return super.putIfAbsent(key, value);
}
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
synchronized (this) {
super.putAll(m);
}
}
@Override
public V remove(Object key) {
synchronized (this) {
return super.remove(key);
}
}
@Override
public void clear() {
synchronized (this) {
super.clear();
}
}
}

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

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -21,10 +21,17 @@ import java.util.HashMap;
import java.util.Map;
import com.jfinal.kit.HashKit;
import com.jfinal.kit.StrKit;
import com.jfinal.kit.SyncWriteMap;
import com.jfinal.template.expr.ast.FieldGetter;
import com.jfinal.template.expr.ast.FieldKeyBuilder;
import com.jfinal.template.expr.ast.FieldKit;
import com.jfinal.template.expr.ast.MethodKit;
import com.jfinal.template.io.EncoderFactory;
import com.jfinal.template.source.ClassPathSourceFactory;
import com.jfinal.template.source.ISource;
import com.jfinal.template.source.ISourceFactory;
import com.jfinal.template.source.StringSource;
import com.jfinal.template.stat.OutputDirectiveFactory;
import com.jfinal.template.stat.Parser;
import com.jfinal.template.stat.ast.Stat;
@ -40,7 +47,7 @@ public class Engine {
public static final String MAIN_ENGINE_NAME = "main";
private static Engine MAIN_ENGINE;
private static Map<String, Engine> engineMap = new HashMap<String, Engine>();
private static Map<String, Engine> engineMap = new HashMap<String, Engine>(64, 0.5F);
// Create main engine
static {
@ -53,7 +60,7 @@ public class Engine {
private EngineConfig config = new EngineConfig();
private ISourceFactory sourceFactory = config.getSourceFactory();
private Map<String, Template> templateCache = new HashMap<String, Template>();
private Map<String, Template> templateCache = new SyncWriteMap<String, Template>(2048, 0.5F);
/**
* Create engine without management of JFinal
@ -123,7 +130,7 @@ public class Engine {
}
/**
* Get template with file name
* Get template by file name
*/
public Template getTemplate(String fileName) {
if (fileName.charAt(0) != '/') {
@ -161,14 +168,6 @@ public class Engine {
/**
* Get template by string content and do not cache the template
*
* 重要StringSource 中的 key = HashKit.md5(content)也即 key
* content 有紧密的对应关系 content 发生变化时 key 值也相应变化
* 因此原先 key 所对应的 Template 缓存对象已无法被获取 getTemplateByString(String)
* String 参数的数量不确定时会引发内存泄漏
*
* getTemplateByString(String, boolean) 中的 String 参数的
* 数量可控并且确定时才可对其使用缓存
*/
public Template getTemplateByString(String content) {
return getTemplateByString(content, false);
@ -176,6 +175,15 @@ public class Engine {
/**
* Get template by string content
*
* 重要StringSource 中的 cacheKey = HashKit.md5(content)也即 cacheKey
* content 有紧密的对应关系 content 发生变化时 cacheKey 值也相应变化
* 因此原先 cacheKey 所对应的 Template 缓存对象已无法被获取 getTemplateByString(String)
* String 参数的数量不确定时会引发内存泄漏
*
* getTemplateByString(String, boolean) 中的 String 参数的
* 数量可控并且确定时才可对其使用缓存
*
* @param content 模板内容
* @param cache true 则缓存 Template否则不缓存
*/
@ -184,37 +192,37 @@ public class Engine {
return buildTemplateBySource(new StringSource(content, cache));
}
String key = HashKit.md5(content);
Template template = templateCache.get(key);
String cacheKey = HashKit.md5(content);
Template template = templateCache.get(cacheKey);
if (template == null) {
template = buildTemplateBySource(new StringSource(content, cache));
templateCache.put(key, template);
templateCache.put(cacheKey, template);
} else if (devMode) {
if (template.isModified()) {
template = buildTemplateBySource(new StringSource(content, cache));
templateCache.put(key, template);
templateCache.put(cacheKey, template);
}
}
return template;
}
/**
* Get template with implementation of ISource
* Get template by implementation of ISource
*/
public Template getTemplate(ISource source) {
String key = source.getKey();
if (key == null) { // key null 则不缓存详见 ISource.getKey() 注释
String cacheKey = source.getCacheKey();
if (cacheKey == null) { // cacheKey null 则不缓存详见 ISource.getCacheKey() 注释
return buildTemplateBySource(source);
}
Template template = templateCache.get(key);
Template template = templateCache.get(cacheKey);
if (template == null) {
template = buildTemplateBySource(source);
templateCache.put(key, template);
templateCache.put(cacheKey, template);
} else if (devMode) {
if (template.isModified()) {
template = buildTemplateBySource(source);
templateCache.put(key, template);
templateCache.put(cacheKey, template);
}
}
return template;
@ -232,7 +240,7 @@ public class Engine {
}
/**
* Add shared function with file
* Add shared function by file
*/
public Engine addSharedFunction(String fileName) {
config.addSharedFunction(fileName);
@ -248,7 +256,7 @@ public class Engine {
}
/**
* Add shared function with files
* Add shared function by files
*/
public Engine addSharedFunction(String... fileNames) {
config.addSharedFunction(fileNames);
@ -271,19 +279,44 @@ public class Engine {
return this;
}
public Engine removeSharedObject(String name) {
config.removeSharedObject(name);
return this;
}
/**
* Set output directive factory
*/
public Engine setOutputDirectiveFactory(IOutputDirectiveFactory outputDirectiveFactory) {
public Engine setOutputDirectiveFactory(OutputDirectiveFactory outputDirectiveFactory) {
config.setOutputDirectiveFactory(outputDirectiveFactory);
return this;
}
/**
* Add directive
* 添加自定义指令
*
* 建议添加自定义指令时明确指定 keepLineBlank 变量值其规则如下
* 1keepLineBlank true 该指令所在行的前后空白字符以及末尾字符 '\n' 将会被保留
* 一般用于具有输出值的指令例如 #date#para 等指令
*
* 2keepLineBlank false 该指令所在行的前后空白字符以及末尾字符 '\n' 将会被删除
* 一般用于没有输出值的指令例如 #for#if#else#end 这种性质的指令
*
* <pre>
* 示例
* addDirective("now", NowDirective.class, true)
* </pre>
*/
public Engine addDirective(String directiveName, Directive directive) {
config.addDirective(directiveName, directive);
public Engine addDirective(String directiveName, Class<? extends Directive> directiveClass, boolean keepLineBlank) {
config.addDirective(directiveName, directiveClass, keepLineBlank);
return this;
}
/**
* 添加自定义指令keepLineBlank 使用默认值
*/
public Engine addDirective(String directiveName, Class<? extends Directive> directiveClass) {
config.addDirective(directiveName, directiveClass);
return this;
}
@ -320,7 +353,7 @@ public class Engine {
}
/**
* Remove shared Method with method name
* Remove shared Method by method name
*/
public Engine removeSharedMethod(String methodName) {
config.removeSharedMethod(methodName);
@ -344,10 +377,10 @@ public class Engine {
}
/**
* Remove template cache with template key
* Remove template cache by cache key
*/
public void removeTemplateCache(String templateKey) {
templateCache.remove(templateKey);
public void removeTemplateCache(String cacheKey) {
templateCache.remove(cacheKey);
}
/**
@ -414,6 +447,13 @@ public class Engine {
return this;
}
/**
* 设置为 ClassPathSourceFactory 的快捷方法
*/
public Engine setToClassPathSourceFactory() {
return setSourceFactory(new ClassPathSourceFactory());
}
public ISourceFactory getSourceFactory() {
return sourceFactory;
}
@ -445,6 +485,30 @@ public class Engine {
return config.getEncoding();
}
/**
* Enjoy 模板引擎对 UTF-8 encoding 做过性能优化某些罕见字符
* 无法被编码可以配置为 JdkEncoderFactory 解决问题:
* engine.setEncoderFactory(new JdkEncoderFactory());
*/
public Engine setEncoderFactory(EncoderFactory encoderFactory) {
config.setEncoderFactory(encoderFactory);
return this;
}
/**
* 配置为 JdkEncoderFactory支持 utf8mb4支持 emoji 表情字符
* 支持各种罕见字符编码
*/
public Engine setToJdkEncoderFactory() {
config.setEncoderFactory(new com.jfinal.template.io.JdkEncoderFactory());
return this;
}
public Engine setWriterBufferSize(int bufferSize) {
config.setWriterBufferSize(bufferSize);
return this;
}
/**
* Engine 独立设置为 devMode 可以方便模板文件在修改后立即生效
* 但如果在 devMode 之下并不希望对 addSharedFunction(...)
@ -466,12 +530,62 @@ public class Engine {
}
public static void removeExtensionMethod(Class<?> targetClass, Object objectOfExtensionClass) {
MethodKit.removeExtensionMethod(targetClass, objectOfExtensionClass);;
MethodKit.removeExtensionMethod(targetClass, objectOfExtensionClass);
}
public static void removeExtensionMethod(Class<?> targetClass, Class<?> extensionClass) {
MethodKit.removeExtensionMethod(targetClass, extensionClass);
}
/**
* 添加 FieldGetter 实现类到指定的位置
*
* 系统当前默认 FieldGetter 实现类及其位置如下
* GetterMethodFieldGetter ---> 调用 getter 方法取值
* RealFieldGetter ---> 直接获取 public 型的 object.field
* ModelFieldGetter ---> 调用 Model.get(String) 方法取值
* RecordFieldGetter ---> 调用 Record.get(String) 方法取值
* MapFieldGetter ---> 调用 Map.get(String) 方法取值
* ArrayLengthGetter ---> 获取数组长度
*
* 根据以上次序如果要插入 IsMethodFieldGetter GetterMethodFieldGetter
* 之后的代码如下
* Engine.addFieldGetter(1, new IsMethodFieldGetter());
*
* IsMethodFieldGetter 系统已经提供只是默认没有启用该实现类通过调用
* target.isXxx() 方法获取 target.xxx 表达式的值其中 isXxx() 返回值
* 必须是 Boolean/boolean 类型才会被调用
*/
public static void addFieldGetter(int index, FieldGetter fieldGetter) {
FieldKit.addFieldGetter(index, fieldGetter);
}
public static void addFieldGetterToLast(FieldGetter fieldGetter) {
FieldKit.addFieldGetterToLast(fieldGetter);
}
public static void addFieldGetterToFirst(FieldGetter fieldGetter) {
FieldKit.addFieldGetterToFirst(fieldGetter);
}
public static void removeFieldGetter(Class<? extends FieldGetter> fieldGetterClass) {
FieldKit.removeFieldGetter(fieldGetterClass);
}
public static void setFastFieldKeyBuilder(boolean enable) {
FieldKeyBuilder.setFastFieldKeyBuilder(enable);
}
/**
* 设置极速模式
*
* 极速模式将生成代理对象来消除 java.lang.reflect.Method.invoke(...) 调用
* 性能提升 12.9%
*/
public static void setFastMode(boolean fastMode) {
FieldKit.setFastMode(fastMode);
FieldKeyBuilder.setFastFieldKeyBuilder(fastMode);
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -19,24 +19,28 @@ package com.jfinal.template;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.jfinal.kit.StrKit;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.expr.ast.SharedMethodKit;
import com.jfinal.template.ext.directive.*;
// import com.jfinal.template.ext.sharedmethod.Json;
import com.jfinal.template.ext.sharedmethod.SharedMethodLib;
import com.jfinal.template.io.EncoderFactory;
import com.jfinal.template.io.WriterBuffer;
import com.jfinal.template.source.FileSource;
import com.jfinal.template.source.FileSourceFactory;
import com.jfinal.template.source.ISource;
import com.jfinal.template.source.ISourceFactory;
import com.jfinal.template.source.StringSource;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.OutputDirectiveFactory;
import com.jfinal.template.stat.Parser;
import com.jfinal.template.stat.ast.Define;
import com.jfinal.template.stat.ast.Output;
import com.jfinal.template.stat.ast.Stat;
/**
* EngineConfig
@ -45,16 +49,21 @@ public class EngineConfig {
public static final String DEFAULT_ENCODING = "UTF-8";
private Map<String, Define> sharedFunctionMap = new HashMap<String, Define>();
WriterBuffer writerBuffer = new WriterBuffer();
private Map<String, Define> sharedFunctionMap = createSharedFunctionMap(); // new HashMap<String, Define>(512, 0.25F);
private List<ISource> sharedFunctionSourceList = new ArrayList<ISource>(); // for devMode only
Map<String, Object> sharedObjectMap = null;
private IOutputDirectiveFactory outputDirectiveFactory = OutputDirectiveFactory.me;
private OutputDirectiveFactory outputDirectiveFactory = OutputDirectiveFactory.me;
private ISourceFactory sourceFactory = new FileSourceFactory();
private Map<String, Stat> directiveMap = new HashMap<String, Stat>();
private Map<String, Class<? extends Directive>> directiveMap = new HashMap<String, Class<? extends Directive>>(64, 0.5F);
private SharedMethodKit sharedMethodKit = new SharedMethodKit();
// 保留指令所在行空白字符的指令
private Set<String> keepLineBlankDirectives = new HashSet<>();
private boolean devMode = false;
private boolean reloadModifiedSharedFunctionInDevMode = true;
private String baseTemplatePath = null;
@ -62,21 +71,29 @@ public class EngineConfig {
private String datePattern = "yyyy-MM-dd HH:mm";
public EngineConfig() {
// 内置指令 #() #include() 需要配置保留指令所在行前后空白字符以及行尾换行字符 '\n'
setKeepLineBlank("output", true);
setKeepLineBlank("include", true);
// Add official directive of Template Engine
addDirective("render", new RenderDirective());
addDirective("date", new DateDirective());
addDirective("escape", new EscapeDirective());
addDirective("string", new StringDirective());
addDirective("random", new RandomDirective());
addDirective("render", RenderDirective.class, true);
addDirective("date", DateDirective.class, true);
addDirective("escape", EscapeDirective.class, true);
addDirective("random", RandomDirective.class, true);
addDirective("number", NumberDirective.class, true);
addDirective("call", CallDirective.class, false);
addDirective("string", StringDirective.class, false);
// Add official shared method of Template Engine
// addSharedMethod(new Json());
addSharedMethod(new SharedMethodLib());
}
/**
* Add shared function with file
*/
public void addSharedFunction(String fileName) {
fileName = fileName.replace("\\", "/");
// FileSource fileSource = new FileSource(baseTemplatePath, fileName, encoding);
ISource source = sourceFactory.getSource(baseTemplatePath, fileName, encoding);
doAddSharedFunction(source, fileName);
@ -174,7 +191,7 @@ public class EngineConfig {
* 开发者可直接使用模板注释功能将不需要的 function 直接注释掉
*/
private synchronized void reloadSharedFunctionSourceList() {
Map<String, Define> newMap = new HashMap<String, Define>();
Map<String, Define> newMap = createSharedFunctionMap();
for (int i = 0, size = sharedFunctionSourceList.size(); i < size; i++) {
ISource source = sharedFunctionSourceList.get(i);
String fileName = source instanceof FileSource ? ((FileSource)source).getFileName() : null;
@ -189,23 +206,33 @@ public class EngineConfig {
this.sharedFunctionMap = newMap;
}
private Map<String, Define> createSharedFunctionMap() {
return new HashMap<String, Define>(512, 0.25F);
}
public synchronized void addSharedObject(String name, Object object) {
if (sharedObjectMap == null) {
sharedObjectMap = new HashMap<String, Object>();
sharedObjectMap = new HashMap<String, Object>(64, 0.25F);
} else if (sharedObjectMap.containsKey(name)) {
throw new IllegalArgumentException("Shared object already exists: " + name);
}
sharedObjectMap.put(name, object);
}
Map<String, Object> getSharedObjectMap() {
public Map<String, Object> getSharedObjectMap() {
return sharedObjectMap;
}
public synchronized void removeSharedObject(String name) {
if (sharedObjectMap != null) {
sharedObjectMap.remove(name);
}
}
/**
* Set output directive factory
*/
public void setOutputDirectiveFactory(IOutputDirectiveFactory outputDirectiveFactory) {
public void setOutputDirectiveFactory(OutputDirectiveFactory outputDirectiveFactory) {
if (outputDirectiveFactory == null) {
throw new IllegalArgumentException("outputDirectiveFactory can not be null");
}
@ -251,8 +278,9 @@ public class EngineConfig {
throw new IllegalArgumentException("baseTemplatePath can not be blank");
}
baseTemplatePath = baseTemplatePath.trim();
baseTemplatePath = baseTemplatePath.replace("\\", "/");
if (baseTemplatePath.length() > 1) {
if (baseTemplatePath.endsWith("/") || baseTemplatePath.endsWith("\\")) {
if (baseTemplatePath.endsWith("/")) {
baseTemplatePath = baseTemplatePath.substring(0, baseTemplatePath.length() - 1);
}
}
@ -268,6 +296,17 @@ public class EngineConfig {
throw new IllegalArgumentException("encoding can not be blank");
}
this.encoding = encoding;
writerBuffer.setEncoding(encoding); // 间接设置 EncoderFactory.encoding
}
public void setEncoderFactory(EncoderFactory encoderFactory) {
writerBuffer.setEncoderFactory(encoderFactory);
writerBuffer.setEncoding(encoding); // 间接设置 EncoderFactory.encoding
}
public void setWriterBufferSize(int bufferSize) {
writerBuffer.setBufferSize(bufferSize);
}
public String getEncoding() {
@ -289,25 +328,46 @@ public class EngineConfig {
this.reloadModifiedSharedFunctionInDevMode = reloadModifiedSharedFunctionInDevMode;
}
public synchronized void addDirective(String directiveName, Directive directive) {
public synchronized void addDirective(String directiveName, Class<? extends Directive> directiveClass, boolean keepLineBlank) {
if (StrKit.isBlank(directiveName)) {
throw new IllegalArgumentException("directive name can not be blank");
}
if (directive == null) {
throw new IllegalArgumentException("directive can not be null");
if (directiveClass == null) {
throw new IllegalArgumentException("directiveClass can not be null");
}
if (directiveMap.containsKey(directiveName)) {
throw new IllegalArgumentException("directive already exists : " + directiveName);
}
directiveMap.put(directiveName, directive);
directiveMap.put(directiveName, directiveClass);
if (keepLineBlank) {
keepLineBlankDirectives.add(directiveName);
}
}
public Stat getDirective(String directiveName) {
public void addDirective(String directiveName, Class<? extends Directive> directiveClass) {
addDirective(directiveName, directiveClass, false);
}
public Class<? extends Directive> getDirective(String directiveName) {
return directiveMap.get(directiveName);
}
public void removeDirective(String 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

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -35,7 +35,7 @@ import com.jfinal.template.stat.ast.Define;
public class Env {
protected EngineConfig engineConfig;
protected Map<String, Define> functionMap = new HashMap<String, Define>();
protected Map<String, Define> functionMap = new HashMap<String, Define>(16, 0.5F);
// 代替 Template 持有该属性便于在 #include 指令中调用 Env.addSource()
protected List<ISource> sourceList = null;
@ -48,6 +48,10 @@ public class Env {
return engineConfig;
}
public boolean isDevMode() {
return engineConfig.isDevMode();
}
/**
* Add template function
*/

View File

@ -1,105 +0,0 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jfinal.template;
import java.io.IOException;
import java.io.Writer;
/**
* FastStringWriter
*
* JDK StringWriter 改造而成 StringBuffer 属性替换为
* StringBuilder避免 StringBuffer synchronized 操作
*/
public class FastStringWriter extends Writer {
private StringBuilder buf;
public FastStringWriter() {
buf = new StringBuilder();
}
public FastStringWriter(int initialSize) {
if (initialSize < 0) {
throw new IllegalArgumentException("Negative buffer size");
}
buf = new StringBuilder(initialSize);
}
public void write(int c) {
buf.append((char) c);
}
public void write(char cbuf[], int off, int len) {
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
((off + len) > cbuf.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
buf.append(cbuf, off, len);
}
public void write(String str) {
buf.append(str);
}
public void write(String str, int off, int len) {
buf.append(str.substring(off, off + len));
}
public FastStringWriter append(CharSequence csq) {
if (csq == null) {
write("null");
} else {
write(csq.toString());
}
return this;
}
public FastStringWriter append(CharSequence csq, int start, int end) {
CharSequence cs = (csq == null ? "null" : csq);
write(cs.subSequence(start, end).toString());
return this;
}
public FastStringWriter append(char c) {
write(c);
return this;
}
public String toString() {
return buf.toString();
}
public StringBuilder getBuffer() {
return buf;
}
public void flush() {
}
public void close() throws IOException {
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -16,8 +16,15 @@
package com.jfinal.template;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.util.Map;
import com.jfinal.template.io.ByteWriter;
import com.jfinal.template.io.CharWriter;
import com.jfinal.template.io.FastStringWriter;
import com.jfinal.template.stat.Scope;
import com.jfinal.template.stat.ast.Stat;
@ -42,27 +49,65 @@ public class Template {
this.ast = ast;
}
/**
* 渲染到 OutputStream 中去
*/
public void render(Map<?, ?> data, OutputStream outputStream) {
ByteWriter byteWriter = env.engineConfig.writerBuffer.getByteWriter(outputStream);
try {
ast.exec(env, new Scope(data, env.engineConfig.sharedObjectMap), byteWriter);
} finally {
byteWriter.close();
}
}
/**
* 支持无 data 参数渲染到 OutputStream 中去 <br>
* 适用于数据在模板中通过表达式和语句直接计算得出等等应用场景
*/
public void render(OutputStream outputStream) {
render(null, outputStream);
}
/**
* 渲染到 Writer 中去
*/
public void render(Map<?, ?> data, Writer writer) {
ast.exec(env, new Scope(data, env.engineConfig.sharedObjectMap), writer);
CharWriter charWriter = env.engineConfig.writerBuffer.getCharWriter(writer);
try {
ast.exec(env, new Scope(data, env.engineConfig.sharedObjectMap), charWriter);
} finally {
charWriter.close();
}
}
/**
* 支持无 data 参数渲染到 Writer 中去 <br>
* 适用于数据在模板中通过表达式和语句直接计算得出等等应用场景<br>
* 此外其它所有 render 方法也支持传入 null data 参数
* 适用于数据在模板中通过表达式和语句直接计算得出等等应用场景
*/
public void render(Writer writer) {
ast.exec(env, new Scope(null, env.engineConfig.sharedObjectMap), writer);
render(null, writer);
}
/**
* 渲染到 FastStringWriter 中去
* 渲染到 String 中去
*/
public void render(Map<?, ?> data, FastStringWriter fastStringWriter) {
ast.exec(env, new Scope(data, env.engineConfig.sharedObjectMap), fastStringWriter);
public String renderToString(Map<?, ?> data) {
FastStringWriter fsw = env.engineConfig.writerBuffer.getFastStringWriter();
try {
render(data, fsw);
return fsw.toString();
} finally {
fsw.close();
}
}
/**
* 支持无 data 参数渲染到 String 中去 <br>
* 适用于数据在模板中通过表达式和语句直接计算得出等等应用场景
*/
public String renderToString() {
return renderToString(null);
}
/**
@ -71,14 +116,27 @@ public class Template {
public StringBuilder renderToStringBuilder(Map<?, ?> data) {
FastStringWriter fsw = new FastStringWriter();
render(data, fsw);
return fsw.getBuffer();
return fsw.toStringBuilder();
}
/**
* 渲染到 String 中去
* 渲染到 File 中去
* 适用于代码生成器类似应用场景
*/
public String renderToString(Map<?, ?> data) {
return renderToStringBuilder(data).toString();
public void render(Map<?, ?> data, File file) {
try (FileOutputStream fos = new FileOutputStream(file)) {
render(data, fos);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 渲染到 String fileName 参数所指定的文件中去
* 适用于代码生成器类似应用场景
*/
public void render(Map<?, ?> data, String fileName) {
render(data, new File(fileName));
}
public boolean isModified() {

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -28,8 +28,8 @@ public class TemplateException extends RuntimeException {
super(loc != null ? msg + loc : msg);
}
public TemplateException(String msg, Location loc, Throwable t) {
super(loc != null ? msg + loc : msg, t);
public TemplateException(String msg, Location loc, Throwable cause) {
super(loc != null ? msg + loc : msg, cause);
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -329,7 +329,7 @@ class ExprLexer {
radix = 16; // 16 进制
c = next();
numStart = numStart + 2;
} else {
} else if (c != '.') {
radix = 8; // 8 进制
// numStart = numStart + 1; // 8 进制不用去掉前缀 0可被正确转换去除此行便于正确处理数字 0
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -21,28 +21,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import com.jfinal.template.EngineConfig;
import com.jfinal.template.expr.Sym;
import com.jfinal.template.expr.ast.Arith;
import com.jfinal.template.expr.ast.Array;
import com.jfinal.template.expr.ast.Assign;
import com.jfinal.template.expr.ast.Compare;
import com.jfinal.template.expr.ast.Const;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.expr.ast.Field;
import com.jfinal.template.expr.ast.ForCtrl;
import com.jfinal.template.expr.ast.Id;
import com.jfinal.template.expr.ast.IncDec;
import com.jfinal.template.expr.ast.Index;
import com.jfinal.template.expr.ast.Logic;
import com.jfinal.template.expr.ast.Map;
import com.jfinal.template.expr.ast.Method;
import com.jfinal.template.expr.ast.NullSafe;
import com.jfinal.template.expr.ast.RangeArray;
import com.jfinal.template.expr.ast.SharedMethod;
import com.jfinal.template.expr.ast.StaticField;
import com.jfinal.template.expr.ast.StaticMethod;
import com.jfinal.template.expr.ast.Ternary;
import com.jfinal.template.expr.ast.Unary;
import com.jfinal.template.expr.ast.*;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParaToken;
import com.jfinal.template.stat.ParseException;
@ -100,6 +79,8 @@ public class ExprParser {
public ForCtrl parseForCtrl() {
Expr forCtrl = parse(false);
// 可能返回 ExprList.NULL_EXPR_LIST必须做判断
if (forCtrl instanceof ForCtrl) {
return (ForCtrl)forCtrl;
} else {
@ -124,7 +105,7 @@ public class ExprParser {
/**
* exprList : expr (',' expr)*
*/
Expr exprList() {
ExprList exprList() {
List<Expr> exprList = new ArrayList<Expr>();
while (true) {
Expr stat = expr();
@ -290,7 +271,7 @@ public class ExprParser {
case ADD:
case SUB:
move();
return new Unary(tok.sym, unary(), location);
return new Unary(tok.sym, unary(), location).toConstIfPossible();
case INC:
case DEC:
move();
@ -346,7 +327,7 @@ public class ExprParser {
return new StaticMethod(clazz, memberName, location);
}
ExprList exprList = (ExprList)exprList();
ExprList exprList = exprList();
match(Sym.RPAREN);
return new StaticMethod(clazz, memberName, exprList, location);
}
@ -383,7 +364,7 @@ public class ExprParser {
return indexMethodField(sharedMethod);
}
ExprList exprList = (ExprList)exprList();
ExprList exprList = exprList();
SharedMethod sharedMethod = new SharedMethod(engineConfig.getSharedMethodKit(), tok.value(), exprList, location);
match(Sym.RPAREN);
return indexMethodField(sharedMethod);
@ -433,7 +414,7 @@ public class ExprParser {
}
// expr '.' ID '(' exprList ')'
ExprList exprList = (ExprList)exprList();
ExprList exprList = exprList();
match(Sym.RPAREN);
expr = new Method(expr, tok.value(), exprList, location);
}
@ -466,21 +447,26 @@ public class ExprParser {
}
/**
* mapEntry : (ID | STR) ':' expr
* mapEntry : (ID | STR | INT | LONG | FLOAT | DOUBLE | TRUE | FALSE | NULL) ':' expr
* 设计目标为 map 定义与初始化所以 ID 仅当成 STR 不进行求值
*/
void buildMapEntry(LinkedHashMap<Object, Expr> map) {
Tok tok = peek();
if (tok.sym == Sym.ID || tok.sym == Sym.STR) {
move();
match(Sym.COLON);
Expr value = expr();
if (value == null) {
throw new ParseException("Expression error: the value on the right side of map entry can not be blank", location);
}
map.put(tok.value(), value);
return ;
Expr keyExpr = expr();
Object key;
if (keyExpr instanceof Id) {
key = ((Id)keyExpr).getId();
} else if (keyExpr instanceof Const) {
key = ((Const)keyExpr).getValue();
} else {
throw new ParseException("Expression error: the value of map key must be identifier, String, Boolean, null or Number", location);
}
throw new ParseException("Expression error: the value of map key must be identifier or String", location);
match(Sym.COLON);
Expr value = expr();
if (value == null) {
throw new ParseException("Expression error: the value on the right side of map entry can not be blank", location);
}
map.put(key, value);
}
/**
@ -498,7 +484,7 @@ public class ExprParser {
move();
return new Array(ExprList.NULL_EXPR_ARRAY, location);
}
ExprList exprList = (ExprList)exprList();
ExprList exprList = exprList();
if (exprList.length() == 1 && peek().sym == Sym.RANGE) {
move();
Expr end = expr();
@ -526,12 +512,14 @@ public class ExprParser {
move();
return new Id(tok.value());
case STR:
move();
return new Const(tok.sym, tok.value());
case INT:
case LONG:
case FLOAT:
case DOUBLE:
move();
return new Const(tok.sym, tok.value());
return new Const(tok.sym, ((NumTok)tok).getNumberValue());
case TRUE:
move();
return Const.TRUE;
@ -560,13 +548,13 @@ public class ExprParser {
/**
* forControl : ID : expr | exprList? ';' expr? ';' exprList?
*/
Expr forCtrl() {
ExprList exprList = (ExprList)exprList();
ForCtrl forCtrl() {
ExprList exprList = exprList();
if (peek().sym == Sym.SEMICOLON) {
move();
Expr cond = expr();
match(Sym.SEMICOLON);
Expr update = exprList();
ExprList update = exprList();
return new ForCtrl(exprList, cond, update, location);
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -43,7 +43,7 @@ import com.jfinal.template.stat.ParseException;
*/
public class NumTok extends Tok {
private Object value;
private Number value;
NumTok(Sym sym, String s, int radix, boolean isScientificNotation, Location location) {
super(sym, location.getRow());
@ -101,3 +101,8 @@ public class NumTok extends Tok {
return sym.value() + " : " + value;
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -70,6 +70,23 @@ public class Assign extends Expr {
this.location = location;
}
/**
* 获取 assign 表达式左侧标识符 id
* 在自定义指令中得到 id 可以得知该赋值表达式是针对哪个变量在操作有助于扩展
* 需求来源http://www.jfinal.com/share/379
*/
public String getId() {
return id;
}
public Expr getIndex() {
return index;
}
public Expr getRight() {
return right;
}
/**
* 赋值语句有返回值可以用于表达式计算
*/

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -94,9 +94,11 @@ public class Compare extends Expr {
case Arith.FLOAT:
// 此法仅适用于两个对象类型相同的情况升级为 BigDecimal 后精度会再高几个数量级
// return Float.floatToIntBits(l.floatValue()) == Float.floatToIntBits(r.floatValue());
return l.floatValue() == r.floatValue();
case Arith.DOUBLE:
// 此法仅适用于两个对象类型相同的情况升级为 BigDecimal 后精度会再高几个数量级
// return Double.doubleToLongBits(l.doubleValue()) == Double.doubleToLongBits(r.doubleValue());
return l.doubleValue() == r.doubleValue();
case Arith.BIGDECIMAL:
BigDecimal[] bd = toBigDecimals(l, r);
return (bd[0]).compareTo(bd[1]) == 0;
@ -120,8 +122,10 @@ public class Compare extends Expr {
return l.longValue() > r.longValue();
case Arith.FLOAT:
// return Float.floatToIntBits(l.floatValue()) > Float.floatToIntBits(r.floatValue());
return l.floatValue() > r.floatValue();
case Arith.DOUBLE:
// return Double.doubleToLongBits(l.doubleValue()) > Double.doubleToLongBits(r.doubleValue());
return l.doubleValue() > r.doubleValue();
case Arith.BIGDECIMAL:
BigDecimal[] bd = toBigDecimals(l, r);
return (bd[0]).compareTo(bd[1]) > 0;
@ -130,11 +134,12 @@ public class Compare extends Expr {
}
if (leftValue instanceof Comparable &&
rightValue != null &&
leftValue.getClass() == rightValue.getClass()) {
return ((Comparable)leftValue).compareTo((Comparable)rightValue) > 0;
}
return checkType(leftValue, rightValue);
return checkComparisonValue(leftValue, rightValue);
}
@SuppressWarnings({"unchecked", "rawtypes"})
@ -150,8 +155,10 @@ public class Compare extends Expr {
return l.longValue() >= r.longValue();
case Arith.FLOAT:
// return Float.floatToIntBits(l.floatValue()) >= Float.floatToIntBits(r.floatValue());
return l.floatValue() >= r.floatValue();
case Arith.DOUBLE:
// return Double.doubleToLongBits(l.doubleValue()) >= Double.doubleToLongBits(r.doubleValue());
return l.doubleValue() >= r.doubleValue();
case Arith.BIGDECIMAL:
BigDecimal[] bd = toBigDecimals(l, r);
return (bd[0]).compareTo(bd[1]) >= 0;
@ -160,11 +167,12 @@ public class Compare extends Expr {
}
if (leftValue instanceof Comparable &&
rightValue != null &&
leftValue.getClass() == rightValue.getClass()) {
return ((Comparable)leftValue).compareTo((Comparable)rightValue) >= 0;
}
return checkType(leftValue, rightValue);
return checkComparisonValue(leftValue, rightValue);
}
@SuppressWarnings({"unchecked", "rawtypes"})
@ -180,8 +188,10 @@ public class Compare extends Expr {
return l.longValue() < r.longValue();
case Arith.FLOAT:
// return Float.floatToIntBits(l.floatValue()) < Float.floatToIntBits(r.floatValue());
return l.floatValue() < r.floatValue();
case Arith.DOUBLE:
// return Double.doubleToLongBits(l.doubleValue()) < Double.doubleToLongBits(r.doubleValue());
return l.doubleValue() < r.doubleValue();
case Arith.BIGDECIMAL:
BigDecimal[] bd = toBigDecimals(l, r);
return (bd[0]).compareTo(bd[1]) < 0;
@ -190,11 +200,12 @@ public class Compare extends Expr {
}
if (leftValue instanceof Comparable &&
rightValue != null &&
leftValue.getClass() == rightValue.getClass()) {
return ((Comparable)leftValue).compareTo((Comparable)rightValue) < 0;
}
return checkType(leftValue, rightValue);
return checkComparisonValue(leftValue, rightValue);
}
@SuppressWarnings({"unchecked", "rawtypes"})
@ -210,8 +221,10 @@ public class Compare extends Expr {
return l.longValue() <= r.longValue();
case Arith.FLOAT:
// return Float.floatToIntBits(l.floatValue()) <= Float.floatToIntBits(r.floatValue());
return l.floatValue() <= r.floatValue();
case Arith.DOUBLE:
// return Double.doubleToLongBits(l.doubleValue()) <= Double.doubleToLongBits(r.doubleValue());
return l.doubleValue() <= r.doubleValue();
case Arith.BIGDECIMAL:
BigDecimal[] bd = toBigDecimals(l, r);
return (bd[0]).compareTo(bd[1]) <= 0;
@ -220,11 +233,12 @@ public class Compare extends Expr {
}
if (leftValue instanceof Comparable &&
rightValue != null &&
leftValue.getClass() == rightValue.getClass()) {
return ((Comparable)leftValue).compareTo((Comparable)rightValue) <= 0;
}
return checkType(leftValue, rightValue);
return checkComparisonValue(leftValue, rightValue);
}
private int getMaxType(Number obj1, Number obj2) {
@ -260,7 +274,7 @@ public class Compare extends Expr {
return ret;
}
private Boolean checkType(Object leftValue, Object rightValue) {
private Boolean checkComparisonValue(Object leftValue, Object rightValue) {
if (leftValue == null) {
throw new TemplateException("The operation target on the left side of \"" + op.value() + "\" can not be null", location);
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -20,62 +20,29 @@ import com.jfinal.template.expr.Sym;
import com.jfinal.template.stat.Scope;
/**
* STR INT LONG FLOAT DOUBLE true false null
* STR INT LONG FLOAT DOUBLE TRUE FALSE NULL
*/
public class Const extends Expr {
public static final Const TRUE = new Const(Boolean.TRUE, Sym.TRUE);
public static final Const FALSE = new Const(Boolean.FALSE, Sym.FALSE);
public static final Const NULL = new Const(null, Sym.NULL);
public static final Const TRUE = new Const(Sym.TRUE, Boolean.TRUE);
public static final Const FALSE = new Const(Sym.FALSE, Boolean.FALSE);
public static final Const NULL = new Const(Sym.NULL, null);
private Sym type;
private Object value;
private final Sym type;
private final Object value;
/**
* 构造 TRUE FALSE NULL 常量无需对 value 进行转换
* INT LONG FLOAT DOUBLE 常量已在 NumTok 中转换成了确切的类型无需再次转换
*/
private Const(Object value, Sym type) {
public Const(Sym type, Object value) {
this.type = type;
this.value = value;
}
public Const(Sym type, String value) {
this.type = type;
this.value = typeConvert(type, value);
}
private Object typeConvert(Sym type, String value) {
switch (type) {
case STR:
return value;
case INT:
return Integer.parseInt(value);
case LONG:
return Long.parseLong(value);
case FLOAT:
return Float.parseFloat(value);
case DOUBLE:
return Double.parseDouble(value);
/*
case TRUE:
case FALSE:
return Boolean.parseBoolean(value);
case NULL:
return null;
*/
default:
throw new RuntimeException("never happend");
}
}
public Object eval(Scope scope) {
return value;
}
public String toString() {
return value.toString();
}
public boolean isStr() {
return type == Sym.STR;
}
@ -112,6 +79,10 @@ public class Const extends Expr {
return type == Sym.DOUBLE;
}
public boolean isNumber() {
return value instanceof Number;
}
public Object getValue() {
return value;
}
@ -139,6 +110,14 @@ public class Const extends Expr {
public Double getDouble() {
return (Double)value;
}
public Number getNumber() {
return (Number)value;
}
public String toString() {
return value != null ? value.toString() : "null";
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -16,6 +16,7 @@
package com.jfinal.template.expr.ast;
import java.util.ArrayList;
import java.util.List;
import com.jfinal.template.TemplateException;
import com.jfinal.template.stat.Scope;
@ -25,24 +26,40 @@ import com.jfinal.template.stat.Scope;
*/
public class ExprList extends Expr {
public static final Expr NULL_EXPR = NullExpr.me;
public static final Expr[] NULL_EXPR_ARRAY = new Expr[0];
public static final ExprList NULL_EXPR_LIST = new ExprList(new ArrayList<Expr>(0));
public static final Object[] NULL_OBJECT_ARRAY = new Object[0];
public static final ExprList NULL_EXPR_LIST = new ExprList();
private Expr[] exprArray;
private ExprList() {
this.exprArray = NULL_EXPR_ARRAY;
}
public ExprList(List<Expr> exprList) {
if (exprList != null && exprList.size() > 0) {
if (exprList.size() > 0) {
exprArray = exprList.toArray(new Expr[exprList.size()]);
} else {
exprArray = NULL_EXPR_ARRAY;
}
}
/**
* 持有 ExprList 的指令可以通过此方法提升 AST 执行性能
* 1 exprArray.length == 1 时返回 exprArray[0]
* 2 exprArray.length == 0 时返回 NullExpr
* 3其它情况返回 ExprList 自身
*
* 意义在于当满足前面两个条件时避免掉了 ExprList.eval(...) 方法中的判断与循环
*/
public Expr getActualExpr() {
if (exprArray.length == 1) {
return exprArray[0];
} else if (exprArray.length == 0) {
return NULL_EXPR;
} else {
return this;
}
}
public Expr[] getExprArray() {
return exprArray;
}
@ -54,6 +71,14 @@ public class ExprList extends Expr {
return exprArray[index];
}
public Expr getFirstExpr() {
return exprArray.length > 0 ? exprArray[0] : null;
}
public Expr getLastExpr() {
return exprArray.length > 0 ? exprArray[exprArray.length - 1] : null;
}
public int length() {
return exprArray.length;
}
@ -62,11 +87,20 @@ public class ExprList extends Expr {
* 对所有表达式求值只返回最后一个表达式的值
*/
public Object eval(Scope scope) {
Object ret = null;
for (Expr expr : exprArray) {
ret = expr.eval(scope);
// 优化绝大多数情况下 length 等于 1
if (exprArray.length == 1) {
return exprArray[0].eval(scope);
}
return ret;
if (exprArray.length == 0) {
return null;
}
int end = exprArray.length - 1;
for (int i=0; i<end; i++) {
exprArray[i].eval(scope);
}
return exprArray[end].eval(scope);
}
/**
@ -87,3 +121,4 @@ public class ExprList extends Expr {

View File

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

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -16,10 +16,8 @@
package com.jfinal.template.expr.ast;
import java.lang.reflect.Array;
import com.jfinal.kit.HashKit;
import com.jfinal.kit.StrKit;
// import com.jfinal.plugin.activerecord.Model;
// import com.jfinal.plugin.activerecord.Record;
import com.jfinal.template.TemplateException;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException;
@ -30,16 +28,17 @@ import com.jfinal.template.stat.Scope;
*
* field 表达式取值优先次序 user.name 为例
* 1假如 user.getName() 存在则优先调用
* 2假如 user Model 子类则调用 user.get("name")
* 3假如 user Record则调用 user.get("name")
* 4假如 user Map则调用 user.get("name")
* 5假如 user 具有 public name 属性则取 user.name 属性值
* 2假如 user 具有 public name 属性则取 user.name 属性值
* 3假如 user Model 子类则调用 user.get("name")
* 4假如 user Record则调用 user.get("name")
* 5假如 user Map则调用 user.get("name")
*/
public class Field extends Expr {
private Expr expr;
private String fieldName;
private String getterName;
private long getterNameHash;
public Field(Expr expr, String fieldName, Location location) {
if (expr == null) {
@ -48,6 +47,8 @@ public class Field extends Expr {
this.expr = expr;
this.fieldName = fieldName;
this.getterName = "get" + StrKit.firstCharToUpperCase(fieldName);
// fnv1a64 hash 到比 String.hashCode() 更大的 long 值范围
this.getterNameHash = HashKit.fnv1a64(getterName);
this.location = location;
}
@ -64,54 +65,34 @@ public class Field extends Expr {
throw new TemplateException("Can not accessed by \"" + fieldName + "\" field from null target", location);
}
Class<?> targetClass = target.getClass();
String key = FieldKit.getFieldKey(targetClass, getterName);
MethodInfo getter;
try {
getter = MethodKit.getGetterMethod(key, targetClass, getterName);
Class<?> targetClass = target.getClass();
Object key = FieldKeyBuilder.instance.getFieldKey(targetClass, getterNameHash);
FieldGetter fieldGetter = FieldKit.getFieldGetter(key, targetClass, fieldName);
if (fieldGetter.notNull()) {
return fieldGetter.get(target, fieldName);
}
} catch (TemplateException | ParseException e) {
throw e;
} catch (Exception e) {
throw new TemplateException(e.getMessage(), location, e);
}
try {
if (getter != null) {
return getter.invoke(target, ExprList.NULL_OBJECT_ARRAY);
}
// if (target instanceof Model) {
// return ((Model<?>)target).get(fieldName);
// }
// if (target instanceof Record) {
// return ((Record)target).get(fieldName);
// }
if (target instanceof java.util.Map) {
return ((java.util.Map<?, ?>)target).get(fieldName);
}
// if (target instanceof com.jfinal.kit.Ret) {
// return ((com.jfinal.kit.Ret)target).get(fieldName);
// }
java.lang.reflect.Field field = FieldKit.getField(key, targetClass, fieldName);
if (field != null) {
return field.get(target);
}
// 支持获取数组长度 array.length
if ("length".equals(fieldName) && target.getClass().isArray()) {
return Array.getLength(target);
}
} catch (Exception e) {
throw new TemplateException(e.getMessage(), location, e);
}
if (scope.getCtrl().isNullSafe()) {
return null;
}
if (expr instanceof Id) {
String id = ((Id)expr).getId();
throw new TemplateException("Field not found: \"" + id + "." + fieldName + "\" and getter method not found: \"" + id + "." + getterName + "()\"", location);
throw new TemplateException("public field not found: \"" + id + "." + fieldName + "\" and public getter method not found: \"" + id + "." + getterName + "()\"", location);
}
throw new TemplateException("Field not found: \"" + fieldName + "\" and getter method not found: \"" + getterName + "()\"", location);
throw new TemplateException("public field not found: \"" + fieldName + "\" and public getter method not found: \"" + getterName + "()\"", location);
}
// private Long buildFieldKey(Class<?> targetClass) {
// return targetClass.getName().hashCode() ^ getterNameHash;
// }
}

View File

@ -0,0 +1,58 @@
/**
* 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;
/**
* FieldGetter 用于支持 target.field 表达式的取值
* 以及支持用户扩展自定义的 FieldGetter 实现方式
*/
public abstract class FieldGetter {
/**
* 接管 target.fieldName 表达式如果可以接管则返回接管对象否则返回 null
* @param targetClass target.fieldName 表达式中 target Class 类型
* @param fieldName target.fieldName 表达式中的 fieldName 部分
* @return 如果可以接管 targetClass.fieldName 则返回接管对象否则返回 null
*/
public abstract FieldGetter takeOver(Class<?> targetClass, String fieldName);
/**
* 获取 target.fieldName 表达式的值
* @param target 目标对象
* @param fieldName 字段名称
* @return target.fieldName 表达式的值
*/
public abstract Object get(Object target, String fieldName) throws Exception;
/**
* 仅仅 NullFieldGetter 会覆盖此方法并返回 false
*
* 用于消除 FieldKit.getFieldGetter(...) 中的 instanceof 判断
* 并且让 Map fieldGetterCache value 值不必使用 Object 类型
* 还消除了 Field.exec(...) 中的 null 值判断
*/
public boolean notNull() {
return true;
}
}

View File

@ -0,0 +1,270 @@
/**
* 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;
import java.lang.reflect.Array;
import com.jfinal.kit.StrKit;
/**
* FieldGetters 封装官方默认 FieldGetter 实现
*/
public class FieldGetters {
/**
* NullFieldGetter
*
* 用于消除 FieldKit.getFieldGetter(...) 中的 instanceof 判断并且让 Map fieldGetterCache
* 中的 value 不必使用 Object 类型还消除了 Field.exec(...) 中的 null 值判断
*/
public static class NullFieldGetter extends FieldGetter {
public static final NullFieldGetter me = new NullFieldGetter();
public boolean notNull() {
return false;
}
public FieldGetter takeOver(Class<?> targetClass, String fieldName) {
throw new RuntimeException("The method takeOver(Class, String) of NullFieldGetter should not be invoked");
}
public Object get(Object target, String fieldName) throws Exception {
throw new RuntimeException("The method get(Object, String) of NullFieldGetter should not be invoked");
}
}
/**
* GetterMethodFieldGetter
*
* 使用 getter 方法获取 target.field 表达式的值
*/
public static class GetterMethodFieldGetter extends FieldGetter {
protected java.lang.reflect.Method getterMethod;
public GetterMethodFieldGetter(java.lang.reflect.Method getterMethod) {
this.getterMethod = getterMethod;
}
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) {
// if (MethodKit.isForbiddenMethod(getterName)) {
// throw new RuntimeException("Forbidden method: " + getterName);
// }
return new GetterMethodFieldGetter(method);
}
}
return null;
}
public Object get(Object target, String fieldName) throws Exception {
return getterMethod.invoke(target, ExprList.NULL_OBJECT_ARRAY);
}
public String toString() {
return getterMethod.toString();
}
}
/**
* IsMethodFieldGetter
*
* 使用 target.isXxx() 方法获取值默认不启用该功能用户可以通过如下方式启用
* Engine.addLastFieldGetter(new FieldGetters.IsMethodFieldGetter());
*/
public static class IsMethodFieldGetter extends FieldGetter {
protected java.lang.reflect.Method isMethod;
// 此构造方法仅为了方便在 Engine.addFieldGetter(...) 添加时不用为构造方法传参
public IsMethodFieldGetter() {
}
public IsMethodFieldGetter(java.lang.reflect.Method isMethod) {
this.isMethod = isMethod;
}
public FieldGetter takeOver(Class<?> targetClass, String fieldName) {
if (MethodKit.isForbiddenClass(targetClass)) {
throw new RuntimeException("Forbidden class: " + targetClass.getName());
}
String isMethodName = "is" + StrKit.firstCharToUpperCase(fieldName);
java.lang.reflect.Method[] methodArray = targetClass.getMethods();
for (java.lang.reflect.Method method : methodArray) {
if (method.getName().equals(isMethodName) && method.getParameterCount() == 0) {
Class<?> returnType = method.getReturnType();
if (returnType == Boolean.class || returnType == boolean.class) {
return new IsMethodFieldGetter(method);
}
}
}
return null;
}
public Object get(Object target, String fieldName) throws Exception {
return isMethod.invoke(target, ExprList.NULL_OBJECT_ARRAY);
}
public String toString() {
return isMethod.toString();
}
}
/**
* ModelFieldGetter
*
* 使用 Model.get(String) 获取值
public static class ModelFieldGetter extends FieldGetter {
// 所有 Model 可以共享 ModelFieldGetter 获取属性
static final ModelFieldGetter singleton = new ModelFieldGetter();
public FieldGetter takeOver(Class<?> targetClass, String fieldName) {
if (com.jfinal.plugin.activerecord.Model.class.isAssignableFrom(targetClass)) {
return singleton;
} else {
return null;
}
}
public Object get(Object target, String fieldName) throws Exception {
return ((com.jfinal.plugin.activerecord.Model<?>)target).get(fieldName);
}
} */
/**
* RecordFieldGetter
*
* 使用 Record.get(String) 获取值
public static class RecordFieldGetter extends FieldGetter {
// 所有 Record 可以共享 RecordFieldGetter 获取属性
static final RecordFieldGetter singleton = new RecordFieldGetter();
public FieldGetter takeOver(Class<?> targetClass, String fieldName) {
if (com.jfinal.plugin.activerecord.Record.class.isAssignableFrom(targetClass)) {
return singleton;
} else {
return null;
}
}
public Object get(Object target, String fieldName) throws Exception {
return ((com.jfinal.plugin.activerecord.Record)target).get(fieldName);
}
} */
/**
* MapFieldGetter
*
* 使用 Map.get(Object) 获取值
*/
public static class MapFieldGetter extends FieldGetter {
// 所有 Map 可以共享 MapFieldGetter 获取属性
static final MapFieldGetter singleton = new MapFieldGetter();
public FieldGetter takeOver(Class<?> targetClass, String fieldName) {
if (java.util.Map.class.isAssignableFrom(targetClass)) {
return singleton;
} else {
return null;
}
}
public Object get(Object target, String fieldName) throws Exception {
return ((java.util.Map<?, ?>)target).get(fieldName);
}
}
/**
* RealFieldGetter
*
* 使用 target.field 获取值
*/
public static class RealFieldGetter extends FieldGetter {
protected java.lang.reflect.Field field;
public RealFieldGetter(java.lang.reflect.Field field) {
this.field = field;
}
public FieldGetter takeOver(Class<?> targetClass, String fieldName) {
java.lang.reflect.Field[] fieldArray = targetClass.getFields();
for (java.lang.reflect.Field field : fieldArray) {
if (field.getName().equals(fieldName)) {
return new RealFieldGetter(field);
}
}
return null;
}
public Object get(Object target, String fieldName) throws Exception {
return field.get(target);
}
public String toString() {
return field.toString();
}
}
/**
* ArrayLengthGetter
*
* 获取数组长度 array.length
*/
public static class ArrayLengthGetter extends FieldGetter {
// 所有数组可以共享 ArrayLengthGetter 获取属性
static final ArrayLengthGetter singleton = new ArrayLengthGetter();
public FieldGetter takeOver(Class<?> targetClass, String fieldName) {
if ("length".equals(fieldName) && targetClass.isArray()) {
return singleton;
} else {
return null;
}
}
public Object get(Object target, String fieldName) throws Exception {
return Array.getLength(target);
}
}
}

View File

@ -0,0 +1,132 @@
/**
* 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;
/**
* FieldKeyBuilder
*
* 用于生成缓存 FieldGetter key
*/
public abstract class FieldKeyBuilder {
public abstract Object getFieldKey(Class<?> targetClass, long fieldFnv1a64Hash);
// 假定是超大规模项目并且假定其 Map/Model/Record + field 组合数量超级庞大默认使用 StrictFieldKeyBuilder
static FieldKeyBuilder instance = new StrictFieldKeyBuilder();
public static FieldKeyBuilder getInstance() {
return instance;
}
/**
* 开启 FastFieldKeyBuilder性能更高
*/
public static void setFastFieldKeyBuilder(boolean enable) {
if (enable) {
instance = new FastFieldKeyBuilder();
} else {
instance = new StrictFieldKeyBuilder();
}
}
/**
* 设置为自定义 FieldKeyBuilder
*/
public static void setFieldKeyBuilder(FieldKeyBuilder fieldKeyBuilder) {
if (fieldKeyBuilder == null) {
throw new IllegalArgumentException("fieldKeyBuilder can not be null");
}
instance = fieldKeyBuilder;
}
// ------------------------------------------------------------------------
/**
* FastFieldKeyBuilder
*/
public static class FastFieldKeyBuilder extends FieldKeyBuilder {
public Object getFieldKey(Class<?> targetClass, long fieldFnv1a64Hash) {
return targetClass.getName().hashCode() ^ fieldFnv1a64Hash;
}
}
// ------------------------------------------------------------------------
/**
* StrictFieldKeyBuilder
*/
public static class StrictFieldKeyBuilder extends FieldKeyBuilder {
public Object getFieldKey(Class<?> targetClass, long fieldFnv1a64Hash) {
return new FieldKey(targetClass.getName().hashCode(), fieldFnv1a64Hash);
}
}
// ------------------------------------------------------------------------
/**
* FieldKey
*
* FieldKey 用于封装 targetClassfieldName 这两部分的 hash
* 确保不会出现 key 值碰撞
*
* 这两部分 hash 值在不同 class field 的组合下出现碰撞的
* 概率完全可以忽略不计
*
* 备忘
* 可以考虑用 ThreadLocal 重用 FieldKey 对象但要注意放入 Map fieldGetterCache
* 中的 FieldKey 对象需要 clone 出来确保线程安全由于 FieldKey 占用空间不大
* 所以 ThreadLocal 方案大概率并无优势 ThreadLocal 中获取数据时除了耗时也无法
* 避免创建对象
*/
public static class FieldKey {
final long classHash;
final long fieldHash;
public FieldKey(long classHash, long fieldHash) {
this.classHash = classHash;
this.fieldHash = fieldHash;
}
public int hashCode() {
return (int)(classHash ^ fieldHash);
}
/**
* FieldKey 的核心价值在于此 equals 方法通过比较两部分 hash 避免超大规模场景下可能的 key 值碰撞
*
* 不必判断 if (fieldKey instanceof FieldKey)因为所有 key 类型必须要相同
* 不必判断 if (this == fieldKey)因为每次用于取值的 FieldKey 都是新建的
*/
public boolean equals(Object fieldKey) {
FieldKey fk = (FieldKey)fieldKey;
return classHash == fk.classHash && fieldHash == fk.fieldHash;
}
public String toString() {
return "classHash = " + classHash + "\nfieldHash = " + fieldHash;
}
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -16,50 +16,166 @@
package com.jfinal.template.expr.ast;
import java.lang.reflect.Field;
import java.util.concurrent.ConcurrentHashMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import com.jfinal.kit.SyncWriteMap;
import com.jfinal.template.expr.ast.FieldGetters.*;
/**
* FieldKit
*/
public class FieldKit {
private static final ConcurrentHashMap<String, Object> fieldCache = new ConcurrentHashMap<String, Object>();
private static FieldGetter[] getters = init();
public static Field getField(String key, Class<?> targetClass, String fieldName) {
Object field = fieldCache.get(key);
if (field == null) {
field = doGetField(targetClass, fieldName);
if (field != null) {
fieldCache.putIfAbsent(key, field);
} else {
// 对于不存在的 Field只进行一次获取操作主要为了支持 null safe未来需要考虑内存泄漏风险
fieldCache.put(key, Boolean.FALSE);
}
}
return field instanceof Field ? (Field)field : null;
private static final HashMap<Object, FieldGetter> fieldGetterCache = new SyncWriteMap<Object, FieldGetter>(1024, 0.25F);
/**
* 初始化官方默认 FieldGetter
*
* 注意
* 默认不启用 IsMethodFieldGetter用户可以通过下面的代码启用
* Engine.addLastFieldGetter(new FieldGetters.IsMethodFieldGetter());
*
* 也可以通过直接调用 target.isXxx() 方法来达到与 target.xxx 表达式相同的目的
*/
private static FieldGetter[] init() {
LinkedList<FieldGetter> ret = new LinkedList<FieldGetter>();
ret.addLast(new GetterMethodFieldGetter(null));
ret.addLast(new RealFieldGetter(null));
// ret.addLast(new ModelFieldGetter());
// ret.addLast(new RecordFieldGetter());
ret.addLast(new MapFieldGetter());
// 挪到第二的位置addSharedObject(..., modelObj) 用法可以获取到 model 中的 public 属性
// ret.addLast(new RealFieldGetter(null));
ret.addLast(new ArrayLengthGetter());
// ret.addLast(new IsMethodFieldGetter());
return ret.toArray(new FieldGetter[ret.size()]);
}
private static Field doGetField(Class<?> targetClass, String fieldName) {
Field[] fs = targetClass.getFields();
for (Field f : fs) {
if (f.getName().equals(fieldName)) {
return f;
public static FieldGetter getFieldGetter(Object key, Class<?> targetClass, String fieldName) {
FieldGetter fieldGetter = fieldGetterCache.get(key);
if (fieldGetter == null) {
fieldGetter = doGetFieldGetter(targetClass, fieldName); // 已确保不会返回 null
fieldGetterCache.putIfAbsent(key, fieldGetter);
}
return fieldGetter;
}
private static FieldGetter doGetFieldGetter(Class<?> targetClass, String fieldName) {
FieldGetter ret;
for (FieldGetter fieldGetter : getters) {
ret = fieldGetter.takeOver(targetClass, fieldName);
if (ret != null) {
return ret;
}
}
return null;
return NullFieldGetter.me;
}
public static void addFieldGetter(int index, FieldGetter fieldGetter) {
addFieldGetter(fieldGetter, index, true);
}
public static void addFieldGetterToLast(FieldGetter fieldGetter) {
addFieldGetter(fieldGetter, null, true);
}
public static void addFieldGetterToFirst(FieldGetter fieldGetter) {
addFieldGetter(fieldGetter, null, false);
}
// Integer index 不为 null boolean addLast 为无效参数
private static synchronized void addFieldGetter(FieldGetter fieldGetter, Integer index, boolean addLast) {
checkParameter(fieldGetter);
LinkedList<FieldGetter> ret = getCurrentFieldGetters();
if (index != null) {
ret.add(index, fieldGetter);
} else {
if (addLast) {
ret.addLast(fieldGetter);
} else {
ret.addFirst(fieldGetter);
}
}
getters = ret.toArray(new FieldGetter[ret.size()]);
}
private static LinkedList<FieldGetter> getCurrentFieldGetters() {
LinkedList<FieldGetter> ret = new LinkedList<FieldGetter>();
for (FieldGetter fieldGetter : getters) {
ret.add(fieldGetter);
}
return ret;
}
private static void checkParameter(FieldGetter fieldGetter) {
if (fieldGetter == null) {
throw new IllegalArgumentException("The parameter fieldGetter can not be null");
}
for (FieldGetter fg : getters) {
if (fg.getClass() == fieldGetter.getClass()) {
throw new RuntimeException("FieldGetter already exists : " + fieldGetter.getClass().getName());
}
}
}
public static synchronized void removeFieldGetter(Class<? extends FieldGetter> fieldGetterClass) {
LinkedList<FieldGetter> ret = getCurrentFieldGetters();
for (Iterator<FieldGetter> it = ret.iterator(); it.hasNext();) {
if (it.next().getClass() == fieldGetterClass) {
it.remove();
}
}
getters = ret.toArray(new FieldGetter[ret.size()]);
}
public static void clearCache() {
fieldGetterCache.clear();
}
/**
* 获取 Field 用于缓存的 key
* 设置极速模式
*
* 极速模式将生成代理对象来消除 java.lang.reflect.Method.invoke(...) 调用
* 性能提升 12.9%
*/
public static String getFieldKey(Class<?> targetClass, String getterName) {
return new StringBuilder(64).append(targetClass.getName())
.append('.').append(getterName).toString();
}
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

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -58,10 +58,10 @@ public class ForCtrl extends Expr {
/**
* exprList? ';' expr? ';' exprList?
*/
public ForCtrl(Expr init, Expr cond, Expr update, Location location) {
this.init = init;
public ForCtrl(ExprList init, Expr cond, ExprList update, Location location) {
this.init = init.getActualExpr();
this.cond = cond;
this.update = update;
this.update = update.getActualExpr();
this.id = null;
this.expr = null;
this.location = location;

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -23,7 +23,7 @@ import com.jfinal.template.stat.Scope;
*/
public class Id extends Expr {
private String id;
private final String id;
public Id(String id) {
this.id = id;

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -43,8 +43,8 @@ public class Index extends Expr {
@SuppressWarnings("rawtypes")
public Object eval(Scope scope) {
Object array = expr.eval(scope);
if (array == null) {
Object target = expr.eval(scope);
if (target == null) {
if (scope.getCtrl().isNullSafe()) {
return null;
}
@ -56,25 +56,30 @@ public class Index extends Expr {
if (scope.getCtrl().isNullSafe()) {
return null;
}
throw new TemplateException("The index of list/array and the key of map can not be null", location);
}
if (array instanceof List) {
if (idx instanceof Integer) {
return ((List<?>)array).get((Integer)idx);
if (target instanceof java.util.Map) {
// Map key 可以是 null不能抛异常
} else {
throw new TemplateException("The index of list and array can not be null", location);
}
throw new TemplateException("The index of list can only be integer", location);
}
if (array instanceof java.util.Map) {
return ((java.util.Map)array).get(idx);
}
if (array.getClass().isArray()) {
if (target instanceof List) {
if (idx instanceof Integer) {
return java.lang.reflect.Array.get(array, (Integer)idx);
return ((List<?>)target).get((Integer)idx);
}
throw new TemplateException("The index of array can only be integer", location);
throw new TemplateException("The index of list must be integer", location);
}
if (target instanceof java.util.Map) {
return ((java.util.Map)target).get(idx);
}
if (target.getClass().isArray()) {
if (idx instanceof Integer) {
return java.lang.reflect.Array.get(target, (Integer)idx);
}
throw new TemplateException("The index of array must be integer", location);
}
throw new TemplateException("Only the list array and map is supported by index access", location);

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -16,10 +16,6 @@
package com.jfinal.template.expr.ast;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.Sym;
import com.jfinal.template.stat.Location;
@ -95,44 +91,22 @@ public class Logic extends Expr {
* 规则
* 1null 返回 false
* 2boolean 类型原值返回
* 3MapConnection(List被包括在内) 返回 size() > 0
* 4数组返回 length > 0
* 5StringStringBuilderStringBuffer 等继承自 CharSequence 类的对象返回 length > 0
* 6Number 类型返回 value != 0
* 7Iterator 返回 hasNext()
* 8其它返回 true
* 3StringStringBuilder 等一切继承自 CharSequence 类的对象返回 length > 0
* 4其它返回 true
*/
public static boolean isTrue(Object v) {
if (v == null) {
return false;
}
if (v instanceof Boolean) {
return (Boolean)v;
}
if (v instanceof Collection) {
return ((Collection<?>)v).size() > 0;
}
if (v instanceof Map) {
return ((Map<?, ?>)v).size() > 0;
}
if (v.getClass().isArray()) {
return Array.getLength(v) > 0;
}
if (v instanceof CharSequence) {
return ((CharSequence)v).length() > 0;
}
if (v instanceof Number) {
if (v instanceof Double) {
return ((Number)v).doubleValue() != 0;
}
if (v instanceof Float) {
return ((Number)v).floatValue() != 0;
}
return ((Number)v).intValue() != 0;
}
if (v instanceof Iterator) {
return ((Iterator<?>)v).hasNext();
}
return true;
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -24,8 +24,10 @@ import com.jfinal.template.stat.Scope;
* Map
*
* 1定义 map 常量
* {k1:123, k2:"abc", 'k3':true, "k4":[1,2,3], k5:1+2}
* 如上所示map定义的 key 可以为 String 或者 id 标识符而右侧的 value 可以是任意的常量与表达式
* {k1:123, k2:"abc", 'k3':true, "k4":[1,2,3], k5:1+2, 1:12, true:"Y", null:"abc"}
* 如上所示map定义的 key 可以为 id 标识符或者 StringIntegerLongBooleannull
* 等常量值 (详见 ExprParser.buildMapEntry(...) 方法)
* 右侧的 value 可以是任意的常量与表达式
*
* 2取值
* 先将 Map 常量赋值给某个变量 #set(map = {...})
@ -34,6 +36,10 @@ import com.jfinal.template.stat.Scope;
* map[expr]
* map.get("k1")
* map.k1
*
* 3不通过中间变量取值
* {1:'自买', 2:'跟买'}.get(1)
* {1:'自买', 2:'跟买'}[2]
*
* 如上所示当以下标方式取值时下标参数可以是 string expr expr 求值以后的值必须也为 string类型
* 当用 map.k1 这类 field 字段取值形式时则是使用 id 标识符而不是 string 形参数

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -24,6 +24,17 @@ import com.jfinal.template.stat.Scope;
/**
* Method : expr '.' ID '(' exprList? ')'
*
* 每次通过 MethodKit.getMethod(...) MethodInfo 而不是用属性持有其对象
* 是为了支持 target 对象的动态类型MethodInfo 中的 Method 被调用 15 次以后
* 会被 JDK 动态生成 GeneratedAccessorXXX 字节码性能不是问题
* 唯一的性能损耗是从 HashMap 中获取 MethodInfo 对象可以忽略不计
*
* 如果在未来通过结合 #dynamic(boolean) 指令来优化需要在 Ctrl 中引入一个
* boolean dynamic = false 变量而不能在 EnvScope 引入该变量
*
* 还需要引入一个 NullMethodInfo 以及 notNull() 方法此优化复杂度提高不少
* 暂时不做此优化
*/
public class Method extends Expr {
@ -65,28 +76,24 @@ public class Method extends Expr {
}
Object[] argValues = exprList.evalExprList(scope);
MethodInfo methodInfo;
try {
methodInfo = MethodKit.getMethod(target.getClass(), methodName, argValues);
} catch (Exception e) {
throw new TemplateException(e.getMessage(), location, e);
}
if (methodInfo == null) {
MethodInfo methodInfo = MethodKit.getMethod(target.getClass(), methodName, argValues);
if (methodInfo.notNull()) {
return methodInfo.invoke(target, argValues);
}
if (scope.getCtrl().isNullSafe()) {
return null;
}
throw new TemplateException(buildMethodNotFoundSignature("Method not found: " + target.getClass().getName() + ".", methodName, argValues), location);
}
try {
return methodInfo.invoke(target, argValues);
throw new TemplateException(buildMethodNotFoundSignature("public method not found: " + target.getClass().getName() + ".", methodName, argValues), location);
} catch (TemplateException | ParseException e) {
throw e;
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t != null) {
throw new TemplateException(t.getMessage(), location, t);
} else {
throw new TemplateException(e.getMessage(), location, e);
}
if (t == null) {t = e;}
throw new TemplateException(t.getMessage(), location, t);
} catch (Exception e) {
throw new TemplateException(e.getMessage(), location, e);
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -17,7 +17,6 @@
package com.jfinal.template.expr.ast;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@ -26,14 +25,14 @@ import java.lang.reflect.Modifier;
*/
public class MethodInfo {
protected final String key;
protected final Long key;
protected final Class<?> clazz;
protected final Method method;
protected final boolean isVarArgs;
protected final Class<?>[] paraTypes;
public MethodInfo(String key, Class<?> clazz, Method method) {
public MethodInfo(Long key, Class<?> clazz, Method method) {
this.key = key;
this.clazz = clazz;
this.method = method;
@ -41,7 +40,7 @@ public class MethodInfo {
this.paraTypes = method.getParameterTypes();
}
public Object invoke(Object target, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
public Object invoke(Object target, Object... args) throws ReflectiveOperationException {
if (isVarArgs) {
return invokeVarArgsMethod(target, args);
} else {
@ -49,7 +48,7 @@ public class MethodInfo {
}
}
protected Object invokeVarArgsMethod(Object target, Object[] argValues) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
protected Object invokeVarArgsMethod(Object target, Object[] argValues) throws ReflectiveOperationException {
Object[] finalArgValues = new Object[paraTypes.length];
int fixedParaLength = paraTypes.length - 1;
@ -64,7 +63,7 @@ public class MethodInfo {
return method.invoke(target, finalArgValues);
}
public String getKey() {
public Long getKey() {
return key;
}
@ -94,6 +93,29 @@ public class MethodInfo {
}
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

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -16,7 +16,6 @@
package com.jfinal.template.expr.ast;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
@ -26,7 +25,7 @@ public class MethodInfoExt extends MethodInfo {
protected Object objectOfExtensionClass;
public MethodInfoExt(Object objectOfExtensionClass, String key, Class<?> clazz, Method method) {
public MethodInfoExt(Object objectOfExtensionClass, Long key, Class<?> clazz, Method method) {
super(key, clazz, method);
this.objectOfExtensionClass = objectOfExtensionClass;
@ -37,7 +36,7 @@ public class MethodInfoExt extends MethodInfo {
// this.paraTypes = newParaTypes;
}
public Object invoke(Object target, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
public Object invoke(Object target, Object... args) throws ReflectiveOperationException {
Object[] finalArgs = new Object[args.length + 1];
finalArgs[0] = target;

View File

@ -0,0 +1,140 @@
/**
* 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;
import com.jfinal.kit.HashKit;
/**
* MethodKeyBuilder
*/
public abstract class MethodKeyBuilder {
/**
* 生成指定 class指定方法名指定方法形参类型的 key 用于缓存
*/
public abstract Long getMethodKey(Class<?> targetClass, String methodName, Class<?>[] argTypes);
// 默认使用 FastMethodKeyBuilder
static MethodKeyBuilder instance = new FastMethodKeyBuilder();
public static MethodKeyBuilder getInstance() {
return instance;
}
/**
* 切换到 StrictMethodKeyBuilder
*
* <pre>
* 特别注意
* 如果希望将 configEngine(Engine me) 中的 Engine 切换到 StrictMethodKeyBuilder
* 需要在 YourJFinalConfig extends JFinalConfig 中利用如下代码块才能生效
* static {
* MethodKeyBuilder.setToStrictMethodKeyBuilder();
* }
*
* 原因是在 com.jfinal.core.Config new Engine() setToStrictMethodKeyBuilder()
* 方法并未生效所以 extension method 生成 method key 时仍然使用的是 FastMethodKeyBuilder
* 以至于在运行时使用 StrictMethodKeyBuilder 生成的 key 找不到 extension method
*
* 后续版本考虑在调用 setToStrictMethodKeyBuilder() 以后重新初始化一下 MethodKit 中的变量
* 原先的 static 初始化方式重构出 synchronized void init() 方法来方便调用
*
* </pre>
*/
public static void setToStrictMethodKeyBuilder() {
instance = new StrictMethodKeyBuilder();
}
/**
* 切换到用户自定义 MethodKeyBuilder
*/
public static void setMethodKeyBuilder(MethodKeyBuilder methodKeyBuilder) {
if (methodKeyBuilder == null) {
throw new IllegalArgumentException("methodKeyBuilder can not be null");
}
instance = methodKeyBuilder;
}
/**
* FastMethodKeyBuilder
*
* targetClassmethodNameargTypes hash 直接使用 String.hashCode()
* String.hashCode() 会被缓存性能更好
*/
public static class FastMethodKeyBuilder extends MethodKeyBuilder {
public Long getMethodKey(Class<?> targetClass, String methodName, Class<?>[] argTypes) {
long hash = HashKit.FNV_OFFSET_BASIS_64;
hash ^= targetClass.getName().hashCode();
hash *= HashKit.FNV_PRIME_64;
hash ^= methodName.hashCode();
hash *= HashKit.FNV_PRIME_64;
if (argTypes != null) {
for (int i=0; i<argTypes.length; i++) {
Class<?> type = argTypes[i];
if (type != null) {
hash ^= type.getName().hashCode();
hash *= HashKit.FNV_PRIME_64;
} else {
hash ^= "null".hashCode();
hash *= HashKit.FNV_PRIME_64;
}
}
}
return hash;
}
}
/**
* StrictMethodKeyBuilder
*
* targetClassmethodNameargTypes 三部分全部使用 fnv1a64 算法计算 hash
*/
public static class StrictMethodKeyBuilder extends MethodKeyBuilder {
public Long getMethodKey(Class<?> targetClass, String methodName, Class<?>[] argTypes) {
long hash = HashKit.FNV_OFFSET_BASIS_64;
hash = fnv1a64(hash, targetClass.getName());
hash = fnv1a64(hash, methodName);
if (argTypes != null) {
for (int i=0; i<argTypes.length; i++) {
Class<?> type = argTypes[i];
if (type != null) {
hash = fnv1a64(hash, type.getName());
} else {
hash = fnv1a64(hash, "null");
}
}
}
return hash;
}
private long fnv1a64(long offsetBasis, String key) {
long hash = offsetBasis;
for(int i=0, size=key.length(); i<size; i++) {
hash ^= key.charAt(i);
hash *= HashKit.FNV_PRIME_64;
}
return hash;
}
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -22,9 +22,8 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.jfinal.kit.HashKit;
import com.jfinal.kit.ReflectKit;
import com.jfinal.kit.SyncWriteMap;
import com.jfinal.template.ext.extensionmethod.ByteExt;
import com.jfinal.template.ext.extensionmethod.DoubleExt;
import com.jfinal.template.ext.extensionmethod.FloatExt;
@ -39,17 +38,20 @@ import com.jfinal.template.ext.extensionmethod.StringExt;
public class MethodKit {
private static final Class<?>[] NULL_ARG_TYPES = new Class<?>[0];
private static final Set<String> forbiddenMethods = new HashSet<String>();
private static final Set<Class<?>> forbiddenClasses = new HashSet<Class<?>>();
private static final Map<Class<?>, Class<?>> primitiveMap = new HashMap<Class<?>, Class<?>>();
private static final ConcurrentHashMap<String, Object> methodCache = new ConcurrentHashMap<String, Object>();
private static final Set<String> forbiddenMethods = new HashSet<String>(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 SyncWriteMap<Long, MethodInfo> methodCache = new SyncWriteMap<Long, MethodInfo>(2048, 0.25F);
// 初始化在模板中调用 method 时所在的被禁止使用类
static {
Class<?>[] cs = {
System.class, Runtime.class, Thread.class, Class.class, ClassLoader.class, File.class,
Compiler.class, InheritableThreadLocal.class, Package.class, Process.class,
RuntimePermission.class, SecurityManager.class, ThreadGroup.class, ThreadLocal.class
RuntimePermission.class, SecurityManager.class, ThreadGroup.class, ThreadLocal.class,
java.lang.reflect.Method.class,
java.lang.reflect.Proxy.class
};
for (Class<?> c : cs) {
forbiddenClasses.add(c);
@ -60,10 +62,10 @@ public class MethodKit {
static {
String[] ms = {
"getClass", "getDeclaringClass", "forName", "newInstance", "getClassLoader",
"getMethod", "getMethods", "getField", "getFields",
"invoke", // "getMethod", "getMethods", // "getField", "getFields",
"notify", "notifyAll", "wait",
"load", "exit", "loadLibrary", "halt",
"stop", "suspend", "resume", "setDaemon", "setPriority",
"exit", "loadLibrary", "halt", // "load",
"stop", "suspend", "resume" // "setDaemon", "setPriority"
};
for (String m : ms) {
forbiddenMethods.add(m);
@ -95,6 +97,14 @@ public class MethodKit {
return forbiddenClasses.contains(clazz);
}
public static void addForbiddenClass(Class<?> clazz) {
forbiddenClasses.add(clazz);
}
public static void removeForbiddenClass(Class<?> clazz) {
forbiddenClasses.remove(clazz);
}
public static boolean isForbiddenMethod(String methodName) {
return forbiddenMethods.contains(methodName);
}
@ -103,37 +113,26 @@ public class MethodKit {
forbiddenMethods.add(methodName);
}
public static MethodInfo getMethod(Class<?> targetClass, String methodName, Object[] argValues) {
Class<?>[] argTypes = getArgTypes(argValues);
String key = getMethodKey(targetClass, methodName, argTypes);
Object method = methodCache.get(key);
if (method == null) {
method = doGetMethod(key, targetClass, methodName, argTypes);
if (method != null) {
methodCache.putIfAbsent(key, method);
} else {
// 对于不存在的 Method只进行一次获取操作主要为了支持 null safe未来需要考虑内存泄漏风险
methodCache.put(key, Boolean.FALSE);
}
}
return method instanceof MethodInfo ? (MethodInfo)method : null;
public static void removeForbiddenMethod(String methodName) {
forbiddenMethods.remove(methodName);
}
/**
* 获取 getter 方法
* 使用与 Field 相同的 key避免生成两次 key值
*/
public static MethodInfo getGetterMethod(String key, Class<?> targetClass, String methodName) {
Object getterMethod = methodCache.get(key);
if (getterMethod == null) {
getterMethod = doGetMethod(key, targetClass, methodName, NULL_ARG_TYPES);
if (getterMethod != null) {
methodCache.putIfAbsent(key, getterMethod);
} else {
methodCache.put(key, Boolean.FALSE);
}
public static void clearCache() {
methodCache.clear();
}
public static MethodInfo getMethod(Class<?> targetClass, String methodName, Object[] argValues) {
Class<?>[] argTypes = getArgTypes(argValues);
Long key = getMethodKey(targetClass, methodName, argTypes);
MethodInfo method = methodCache.get(key);
if (method == null) {
// 已确保不会返回 null对于不存在的 Method只进行一次获取操作
// 提升 null safe 表达式性能未来需要考虑内存泄漏风险
method = doGetMethod(key, targetClass, methodName, argTypes);
methodCache.putIfAbsent(key, method);
}
return getterMethod instanceof MethodInfo ? (MethodInfo)getterMethod : null;
return method;
}
static Class<?>[] getArgTypes(Object[] argValues) {
@ -147,11 +146,13 @@ public class MethodKit {
return argTypes;
}
private static MethodInfo doGetMethod(String key, Class<?> targetClass, String methodName, Class<?>[] argTypes) {
private static MethodInfo doGetMethod(Long key, Class<?> targetClass, String methodName, Class<?>[] argTypes) {
if (forbiddenClasses.contains(targetClass)) {
throw new RuntimeException("Forbidden class: " + targetClass.getName());
}
// 仅开启 forbiddenClasses 检测
// MethodSharedMethodStaticMethod 已用 MethodKit.isForbiddenMethod(...) 检测
// if (forbiddenMethods.contains(methodName)) {
// throw new RuntimeException("Forbidden method: " + methodName);
// }
@ -168,7 +169,8 @@ public class MethodKit {
}
}
}
return null;
return NullMethodInfo.me;
}
static boolean matchFixedArgTypes(Class<?>[] paraTypes, Class<?>[] argTypes) {
@ -229,23 +231,8 @@ public class MethodKit {
/**
* 获取方法用于缓存的 key
*/
private static String getMethodKey(Class<?> targetClass, String methodName, Class<?>[] argTypes) {
StringBuilder key = new StringBuilder(96);
key.append(targetClass.getName());
key.append('.').append(methodName);
if (argTypes != null && argTypes.length > 0) {
createArgTypesDigest(argTypes, key);
}
return key.toString();
}
static void createArgTypesDigest(Class<?>[] argTypes, StringBuilder key) {
StringBuilder argTypesDigest = new StringBuilder(64);
for (int i=0; i<argTypes.length; i++) {
Class<?> type = argTypes[i];
argTypesDigest.append(type != null ? type.getName() : "null");
}
key.append(HashKit.md5(argTypesDigest.toString()));
private static Long getMethodKey(Class<?> targetClass, String methodName, Class<?>[] argTypes) {
return MethodKeyBuilder.instance.getMethodKey(targetClass, methodName, argTypes);
}
// 以下代码实现 extension method 功能 --------------------
@ -290,13 +277,13 @@ public class MethodKit {
throw new RuntimeException("Extension method \"" + methodName + "\" is already exists in class \"" + targetClass.getName() + "\"");
}
} catch (NoSuchMethodException e) { // Method 找不到才能添加该扩展方法
String key = MethodKit.getMethodKey(targetClass, methodName, toBoxedType(targetParaTypes));
Long key = MethodKit.getMethodKey(targetClass, methodName, toBoxedType(targetParaTypes));
if (methodCache.containsKey(key)) {
throw new RuntimeException(buildMethodSignatureForException("The extension method is already exists: " + extensionClass.getName() + ".", methodName, targetParaTypes));
}
MethodInfoExt mie = new MethodInfoExt(objectOfExtensionClass, key, extensionClass/* targetClass */, method);
methodCache.put(key, mie);
methodCache.putIfAbsent(key, mie);
}
}
}
@ -319,12 +306,12 @@ public class MethodKit {
Class<?>[] targetParaTypes = new Class<?>[extensionMethodParaTypes.length - 1];
System.arraycopy(extensionMethodParaTypes, 1, targetParaTypes, 0, targetParaTypes.length);
String key = MethodKit.getMethodKey(targetClass, methodName, toBoxedType(targetParaTypes));
Long key = MethodKit.getMethodKey(targetClass, methodName, toBoxedType(targetParaTypes));
methodCache.remove(key);
}
}
private static final Map<Class<?>, Class<?>> primitiveToBoxedMap = new HashMap<Class<?>, Class<?>>();
private static final Map<Class<?>, Class<?>> primitiveToBoxedMap = new HashMap<Class<?>, Class<?>>(64);
// 初始化 primitive type boxed type 的映射
static {

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -14,21 +14,23 @@
* limitations under the License.
*/
package com.jfinal.template;
package com.jfinal.template.expr.ast;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ast.Output;
import com.jfinal.template.stat.Scope;
/**
* OutputDirectiveFactory
* NullExpr
*/
public class OutputDirectiveFactory implements IOutputDirectiveFactory {
public class NullExpr extends Expr {
public static final OutputDirectiveFactory me = new OutputDirectiveFactory();
public static final NullExpr me = new NullExpr();
public Output getOutputDirective(ExprList exprList, Location location) {
return new Output(exprList, location);
private NullExpr() {}
public Object eval(Scope scope) {
return null;
}
}

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

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -49,15 +49,18 @@ public class NullSafe extends Expr {
Ctrl ctrl = scope.getCtrl();
boolean oldNullSafeValue = ctrl.isNullSafe();
Object ret;
try {
ctrl.setNullSafe(true);
ret = left.eval(scope);
Object ret = left.eval(scope);
if (ret != null) {
return ret;
}
} finally {
ctrl.setNullSafe(oldNullSafeValue);
}
return ret == null && right != null ? right.eval(scope) : ret;
// right 表达式处于 null safe 区域之外
return right != null ? right.eval(scope) : null;
}
}
@ -66,3 +69,4 @@ public class NullSafe extends Expr {

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -26,9 +26,14 @@ import com.jfinal.template.stat.Scope;
* SharedMethod
*
* 用法
* engine.addSharedMethod(object);
* engine.addSharedStaticMethod(Xxx.class);
* #(method(para))
* engine.addSharedMethod(new StrKit());
* engine.addSharedStaticMethod(MyKit.class);
*
* #if (notBlank(para))
* ....
* #end
*
* 上面代码中的 notBlank 方法来自 StrKit
*/
public class SharedMethod extends Expr {
@ -48,14 +53,18 @@ public class SharedMethod extends Expr {
public Object eval(Scope scope) {
Object[] argValues = exprList.evalExprList(scope);
SharedMethodInfo sharedMethodInfo = sharedMethodKit.getSharedMethodInfo(methodName, argValues);
// ShareMethod 相当于是固定的静态的方法不支持 null safenull safe 只支持具有动态特征的用法
if (sharedMethodInfo == null) {
throw new TemplateException(Method.buildMethodNotFoundSignature("Shared method not found: ", methodName, argValues), location);
}
try {
return sharedMethodInfo.invoke(argValues);
SharedMethodInfo sharedMethodInfo = sharedMethodKit.getSharedMethodInfo(methodName, argValues);
if (sharedMethodInfo != null) {
return sharedMethodInfo.invoke(argValues);
} else {
// ShareMethod 相当于是固定的静态的方法不支持 null safenull safe 只支持具有动态特征的用法
throw new TemplateException(Method.buildMethodNotFoundSignature("Shared method not found: ", methodName, argValues), location);
}
} catch (TemplateException | ParseException e) {
throw e;
} catch (Exception e) {
throw new TemplateException(e.getMessage(), location, e);
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -21,40 +21,41 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import com.jfinal.kit.HashKit;
import com.jfinal.kit.ReflectKit;
import com.jfinal.kit.SyncWriteMap;
/**
* SharedMethodKit
*/
public class SharedMethodKit {
private static final Set<String> excludedMethodKey = new HashSet<String>();
private static final Set<Long> excludedMethodKey = new HashSet<Long>();
static {
Method[] methods = Object.class.getMethods();
for (Method method : methods) {
String key = getSharedMethodKey(method.getName(), method.getParameterTypes());
Long key = getSharedMethodKey(method.getName(), method.getParameterTypes());
excludedMethodKey.add(key);
}
}
private final List<SharedMethodInfo> sharedMethodList = new ArrayList<SharedMethodInfo>();
private final ConcurrentHashMap<String, SharedMethodInfo> methodCache = new ConcurrentHashMap<String, SharedMethodInfo>();
private final HashMap<Long, SharedMethodInfo> methodCache = new SyncWriteMap<Long, SharedMethodInfo>(512, 0.25F);
public SharedMethodInfo getSharedMethodInfo(String methodName, Object[] argValues) {
Class<?>[] argTypes = MethodKit.getArgTypes(argValues);
String key = getSharedMethodKey(methodName, argTypes);
Long key = getSharedMethodKey(methodName, argTypes);
SharedMethodInfo method = methodCache.get(key);
if (method == null) {
method = doGetSharedMethodInfo(methodName, argTypes);
if (method != null) {
methodCache.putIfAbsent(key, method);
}
// shared method 不支持 null safe不缓存: methodCache.put(key, Boolean.FALSE)
// shared method 不支持 null safe不缓存: methodCache.putIfAbsent(key, Void.class)
}
return method;
}
@ -110,7 +111,7 @@ public class SharedMethodKit {
SharedMethodInfo current = it.next();
String methodName = method.getName();
if (current.getName().equals(methodName)) {
String key = getSharedMethodKey(methodName, method.getParameterTypes());
Long key = getSharedMethodKey(methodName, method.getParameterTypes());
if (current.getKey().equals(key)) {
it.remove();
}
@ -125,7 +126,7 @@ public class SharedMethodKit {
Method[] methods = sharedClass.getMethods();
for (Method method : methods) {
String key = getSharedMethodKey(method.getName(), method.getParameterTypes());
Long key = getSharedMethodKey(method.getName(), method.getParameterTypes());
if (excludedMethodKey.contains(key)) {
continue ;
}
@ -144,24 +145,35 @@ public class SharedMethodKit {
}
}
private static String getSharedMethodKey(String methodName, Class<?>[] argTypes) {
StringBuilder key = new StringBuilder(64);
key.append(methodName);
if (argTypes != null && argTypes.length > 0) {
MethodKit.createArgTypesDigest(argTypes, key);
private static Long getSharedMethodKey(String methodName, Class<?>[] argTypes) {
long hash = HashKit.FNV_OFFSET_BASIS_64;
hash ^= methodName.hashCode();
hash *= HashKit.FNV_PRIME_64;
if (argTypes != null) {
for (int i=0; i<argTypes.length; i++) {
Class<?> type = argTypes[i];
if (type != null) {
hash ^= type.getName().hashCode();
hash *= HashKit.FNV_PRIME_64;
} else {
hash ^= "null".hashCode();
hash *= HashKit.FNV_PRIME_64;
}
}
}
return key.toString();
}
return hash;
}
static class SharedMethodInfo extends MethodInfo {
final Object target;
private SharedMethodInfo(String key, Class<?> clazz, Method method, Object target) {
private SharedMethodInfo(Long key, Class<?> clazz, Method method, Object target) {
super(key, clazz, method);
this.target = target;
}
public Object invoke(Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
public Object invoke(Object... args) throws ReflectiveOperationException {
return super.invoke(target, args);
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -50,6 +50,14 @@ public class StaticMethod extends Expr {
} catch (Exception 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.exprList = exprList;
this.location = location;
@ -57,23 +65,23 @@ public class StaticMethod extends Expr {
public Object eval(Scope scope) {
Object[] argValues = exprList.evalExprList(scope);
MethodInfo methodInfo;
try {
methodInfo = MethodKit.getMethod(clazz, methodName, argValues);
} catch (Exception e) {
throw new TemplateException(e.getMessage(), location, e);
}
// StaticMethod 是固定的存在不支持 null safenull safe 只支持具有动态特征的用法
if (methodInfo == null) {
throw new TemplateException(Method.buildMethodNotFoundSignature("public static method not found: " + clazz.getName() + "::", methodName, argValues), location);
}
if (!methodInfo.isStatic()) {
throw new TemplateException(Method.buildMethodNotFoundSignature("Not public static method: " + clazz.getName() + "::", methodName, argValues), location);
}
try {
return methodInfo.invoke(null, argValues);
MethodInfo methodInfo = MethodKit.getMethod(clazz, methodName, argValues);
if (methodInfo.notNull()) {
if (methodInfo.isStatic()) {
return methodInfo.invoke(null, argValues);
} else {
throw new TemplateException(Method.buildMethodNotFoundSignature("Not public static method: " + clazz.getName() + "::", methodName, argValues), location);
}
} else {
// StaticMethod 是固定的存在不支持 null safenull safe 只支持具有动态特征的用法
throw new TemplateException(Method.buildMethodNotFoundSignature("public static method not found: " + clazz.getName() + "::", methodName, argValues), location);
}
} catch (TemplateException | ParseException e) {
throw e;
} catch (Exception e) {
throw new TemplateException(e.getMessage(), location, e);
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -83,6 +83,51 @@ public class Unary extends Expr {
throw new TemplateException("Unsupported operator: " + op.value(), location);
}
}
/**
* 如果可能的话 Unary 表达式转化成 Const 表达式类似于 ExprParser.buildMapEntry() 需要这种转化来简化实现
* 除了可简化程序外还起到一定的性能优化作用
*
* Number : +123 -456 +3.14 -0.12
* Boolean : !true !false
*
* 特别注意
* Boolean 的支持并不需要!true!false 已在 ExprParser 中被 Logic 表达式接管在此仅为逻辑上的完备性而添加
*/
public Expr toConstIfPossible() {
if (expr instanceof Const && (op == Sym.SUB || op == Sym.ADD || op == Sym.NOT)) {
} else {
return this;
}
Expr ret = this;
Const c = (Const)expr;
if (op == Sym.SUB) {
if (c.isInt()) {
ret = new Const(Sym.INT, -c.getInt());
} else if (c.isLong()) {
ret = new Const(Sym.LONG, -c.getLong());
} else if (c.isFloat()) {
ret = new Const(Sym.FLOAT, -c.getFloat());
} else if (c.isDouble()) {
ret = new Const(Sym.DOUBLE, -c.getDouble());
}
} else if (op == Sym.ADD) {
if (c.isNumber()) {
ret = c;
}
} else if (op == Sym.NOT) {
if (c.isBoolean()) {
ret = c.isTrue() ? Const.FALSE : Const.TRUE;
}
}
return ret;
}
public String toString() {
return op.toString() + expr.toString();
}
}

View File

@ -0,0 +1,109 @@
/**
* 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.ext.directive;
import java.util.ArrayList;
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;
import com.jfinal.template.stat.ast.Define;
/**
* CallDirective 动态调用模板函数
*
* 模板函数的名称与参数都可以动态指定提升模板函数调用的灵活性
*
* 例如
* #call(funcName, p1, p2, ..., pn)
* 其中 funcName为函数名p1p2pn 为被调用函数所使用的参数
*
*
* 如果希望模板函数不存在时忽略其调用添加常量值 true 在第一个参数位置即可
* 例如
* #call(true, funcName, p1, p2, ..., pn)
*
*
* TODO 后续优化看一下 ast.Call.java
*/
public class CallDirective extends Directive {
protected Expr funcNameExpr;
protected ExprList paraExpr;
protected boolean nullSafe = false; // 是否支持函数名不存在时跳过
public void setExprList(ExprList exprList) {
int len = exprList.length();
if (len == 0) {
throw new ParseException("模板函数名不能缺失", location);
}
int index = 0;
Expr expr = exprList.getExpr(index);
if (expr instanceof Const && ((Const)expr).isBoolean()) {
if (len == 1) {
throw new ParseException("模板函数名不能缺失", location);
}
nullSafe = ((Const)expr).getBoolean();
index++;
}
funcNameExpr = exprList.getExpr(index++);
ArrayList<Expr> list = new ArrayList<Expr>();
for (int i=index; i<len; i++) {
list.add(exprList.getExpr(i));
}
paraExpr = new ExprList(list);
}
public void exec(Env env, Scope scope, Writer writer) {
Object funcNameValue = funcNameExpr.eval(scope);
if (funcNameValue == null) {
if (nullSafe) {
return ;
}
throw new TemplateException("模板函数名为 null", location);
}
if (!(funcNameValue instanceof String)) {
throw new TemplateException("模板函数名必须是字符串", location);
}
Define func = env.getFunction(funcNameValue.toString());
if (func == null) {
if (nullSafe) {
return ;
}
throw new TemplateException("模板函数未找到 : " + funcNameValue, location);
}
func.call(env, scope, paraExpr, writer);
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -16,24 +16,28 @@
package com.jfinal.template.ext.directive;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.io.IOException;
import java.util.Date;
import com.jfinal.template.Directive;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
/**
* 不带参时按默认 pattern 输出当前日期
* #date 日期格式化输出指令
*
* #date() 指令支持无参时获取当前指令第一个参数 string 当成是 pattern
* 三种用法
* 1#date(createAt) 用默认 datePattern 配置输出 createAt 变量中的日期值
* 2#date(createAt, "yyyy-MM-dd HH:mm:ss") 用第二个参数指定的 datePattern输出 createAt 变量中的日期值
* 3#date() 用默认 datePattern 配置输出 当前 日期值
*
* 日期输出指令第一个参数是被输出的 java.util.Date 对象或其子类对象
* 无第二个参数时按默认 patter 输出第二个参数为 expr 表达式表示 pattern
* 第二个为 date 表示当第一个为 null 时的默认值
* 注意
* 1#date 指令中的参数可以是变量例如#date(d, p) 中的 d p 可以全都是变量
* 2默认 datePattern 可通过 Engine.setDatePattern(...) 进行配置
*/
public class DateDirective extends Directive {
@ -51,55 +55,55 @@ public class DateDirective extends Directive {
this.valueExpr = null;
this.datePatternExpr = null;
} else if (paraNum == 1) {
this.valueExpr = exprList.getExprArray()[0];
this.valueExpr = exprList.getExpr(0);
this.datePatternExpr = null;
} else if (paraNum == 2) {
this.valueExpr = exprList.getExprArray()[0];
this.datePatternExpr = exprList.getExprArray()[1];
this.valueExpr = exprList.getExpr(0);
this.datePatternExpr = exprList.getExpr(1);
}
}
public void exec(Env env, Scope scope, Writer writer) {
if (paraNum == 0) {
outputToday(env, writer);
} else if (paraNum == 1) {
if (paraNum == 1) {
outputWithoutDatePattern(env, scope, writer);
} else if (paraNum == 2) {
outputWithDatePattern(env, scope, writer);
} else {
outputToday(env, writer);
}
}
private void outputToday(Env env, Writer writer) {
Object value = format(new java.util.Date(), env.getEngineConfig().getDatePattern());
write(writer, value.toString());
write(writer, new Date(), env.getEngineConfig().getDatePattern());
}
private void outputWithoutDatePattern(Env env, Scope scope, Writer writer) {
Object value = valueExpr.eval(scope);
if (value != null) {
value = format(value, env.getEngineConfig().getDatePattern());
write(writer, value.toString());
if (value instanceof Date) {
write(writer, (Date)value, env.getEngineConfig().getDatePattern());
} else if (value != null) {
throw new TemplateException("The first parameter date of #date directive must be Date type", location);
}
}
private void outputWithDatePattern(Env env, Scope scope, Writer writer) {
Object value = valueExpr.eval(scope);
if (value == null) {
return ;
if (value instanceof Date) {
Object datePattern = this.datePatternExpr.eval(scope);
if (datePattern instanceof String) {
write(writer, (Date)value, (String)datePattern);
} else {
throw new TemplateException("The sencond parameter datePattern of #date directive must be String", location);
}
} else if (value != null) {
throw new TemplateException("The first parameter date of #date directive must be Date type", location);
}
Object dp = this.datePatternExpr.eval(scope);
if ( !(dp instanceof String) ) {
throw new TemplateException("The sencond parameter dataPattern of #date directive must be String", location);
}
value = format(value, (String)dp);
write(writer, value.toString());
}
private String format(Object value, String datePattern) {
private void write(Writer writer, Date date, String datePattern) {
try {
return new SimpleDateFormat(datePattern).format(value);
} catch (Exception e) {
writer.write(date, datePattern);
} catch (IOException e) {
throw new TemplateException(e.getMessage(), location, e);
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -16,9 +16,12 @@
package com.jfinal.template.ext.directive;
import java.io.Writer;
import java.io.IOException;
import com.jfinal.template.Directive;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
/**
@ -29,44 +32,62 @@ import com.jfinal.template.stat.Scope;
public class EscapeDirective extends Directive {
public void exec(Env env, Scope scope, Writer writer) {
Object value = exprList.eval(scope);
if (value != null) {
write(writer, escape(value.toString()));
try {
Object value = exprList.eval(scope);
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 String escape(String str) {
if (str == null || str.length() == 0) {
return str;
}
int len = str.length();
StringBuilder ret = new StringBuilder(len * 2);
for (int i = 0; i < len; i++) {
private void escape(String str, Writer w) throws IOException {
for (int i = 0, len = str.length(); i < len; i++) {
char cur = str.charAt(i);
switch (cur) {
case '<':
ret.append("&lt;");
w.write("&lt;");
break;
case '>':
ret.append("&gt;");
w.write("&gt;");
break;
case '\"':
ret.append("&quot;");
case '"':
w.write("&quot;");
break;
case '\'':
ret.append("&apos;"); // IE 不支持 &apos; 考虑 &#39;
// w.write("&apos;"); // IE 不支持 &apos; 考虑 &#39;
w.write("&#39;");
break;
case '&':
ret.append("&amp;");
w.write("&amp;");
break;
default:
ret.append(cur);
w.write(str, i, 1);
break;
}
}
return ret.toString();
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -16,12 +16,13 @@
package com.jfinal.template.ext.directive;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.io.IOException;
import java.util.Date;
import com.jfinal.template.Directive;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
@ -31,7 +32,7 @@ import com.jfinal.template.stat.Scope;
*/
public class NowDirective extends Directive {
public void setExrpList(ExprList exprList) {
public void setExprList(ExprList exprList) {
if (exprList.length() > 1) {
throw new ParseException("#now directive support one parameter only", location);
}
@ -39,22 +40,21 @@ public class NowDirective extends Directive {
}
public void exec(Env env, Scope scope, Writer writer) {
String dataPattern;
String datePattern;
if (exprList.length() == 0) {
dataPattern = env.getEngineConfig().getDatePattern();
datePattern = env.getEngineConfig().getDatePattern();
} else {
Object dp = exprList.eval(scope);
if (dp instanceof String) {
dataPattern = (String)dp;
datePattern = (String)dp;
} else {
throw new TemplateException("The parameter of #new directive must be String", location);
throw new TemplateException("The parameter of #now directive must be String", location);
}
}
try {
String value = new SimpleDateFormat(dataPattern).format(new java.util.Date());
write(writer, value);
} catch (Exception e) {
writer.write(new Date(), datePattern);
} catch (IOException e) {
throw new TemplateException(e.getMessage(), location, e);
}
}

View File

@ -0,0 +1,102 @@
/**
* 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.ext.directive;
import java.text.DecimalFormat;
import com.jfinal.template.Directive;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
/**
* #number 数字格式化输出指令
*
* 两种用法
* 1#number(n) 用默认 pattern 输出变量中的值
* 2#number(n, "#.##") 用第二个参数指定的 pattern 输出变量中的值
*
* 注意
* 1pattern 的使用与 java.text.DecimalFormat 的完全一样
* 在拿不定主意的时候可以在搜索引擎中搜索关键字DecimalFormat
* 2#number 指令中的参数可以是变量例如#number(n, p) 中的 n p 可以全都是变量
*
* <pre>
* 示例
* #number(3.1415926, "#.##")
* #number(0.9518, "#.##%")
* #number(300000, "光速为每秒 ,### 公里。")
*
* #set(n = 1.234)
* #set(p = "#.##")
* #number(n, p)
* </pre>
*/
public class NumberDirective extends Directive {
private Expr valueExpr;
private Expr patternExpr;
public void setExprList(ExprList exprList) {
int paraNum = exprList.length();
if (paraNum == 0) {
throw new ParseException("The parameter of #number directive can not be blank", location);
}
if (paraNum > 2) {
throw new ParseException("Wrong number parameter of #number directive, two parameters allowed at most", location);
}
valueExpr = exprList.getExpr(0);
patternExpr = (paraNum == 1 ? null : exprList.getExpr(1));
}
public void exec(Env env, Scope scope, Writer writer) {
Object value = valueExpr.eval(scope);
if (value == null) {
return ;
}
if (patternExpr == null) {
outputWithoutPattern(writer, value);
} else {
outputWithPattern(scope, writer, value);
}
}
private void outputWithoutPattern(Writer writer, Object value) {
String ret = new DecimalFormat().format(value);
write(writer, ret);
}
private void outputWithPattern(Scope scope, Writer writer, Object value) {
Object pattern = patternExpr.eval(scope);
if ( !(pattern instanceof String) ) {
throw new TemplateException("The sencond parameter pattern of #number directive must be String", location);
}
String ret = new DecimalFormat((String)pattern).format(value);
write(writer, ret);
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -16,22 +16,29 @@
package com.jfinal.template.ext.directive;
import java.io.Writer;
import java.io.IOException;
import com.jfinal.template.Directive;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope;
/**
* 输出随机数
* 输出 int 随机数
*/
public class RandomDirective extends Directive {
private java.util.Random random = new java.util.Random();
public void exec(Env env, Scope scope, Writer writer) {
write(writer, String.valueOf(random.nextInt()));
try {
writer.write(random.nextInt());
} catch (IOException e) {
throw new TemplateException(e.getMessage(), location, e);
}
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -16,15 +16,15 @@
package com.jfinal.template.ext.directive;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import com.jfinal.kit.SyncWriteMap;
import com.jfinal.template.Directive;
import com.jfinal.template.EngineConfig;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.Assign;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.source.ISource;
import com.jfinal.template.stat.Ctrl;
import com.jfinal.template.stat.ParseException;
@ -33,6 +33,7 @@ import com.jfinal.template.stat.Scope;
import com.jfinal.template.stat.ast.Define;
import com.jfinal.template.stat.ast.Include;
import com.jfinal.template.stat.ast.Stat;
import com.jfinal.template.stat.ast.StatList;
/**
* #render 指令用于动态渲染子模板作为 include 指令的补充
@ -59,7 +60,7 @@ import com.jfinal.template.stat.ast.Stat;
public class RenderDirective extends Directive {
private String parentFileName;
private Map<String, StatInfo> statInfoCache = new HashMap<String,StatInfo>();
private Map<String, SubStat> subStatCache = new SyncWriteMap<String, SubStat>(16, 0.5F);
public void setExprList(ExprList exprList) {
int len = exprList.length();
@ -69,7 +70,7 @@ public class RenderDirective extends Directive {
if (len > 1) {
for (int i = 1; i < len; i++) {
if (!(exprList.getExpr(i) instanceof Assign)) {
throw new ParseException("The " + i + "th parameter of #render directive must be an assignment expression", location);
throw new ParseException("The " + (i + 1) + "th parameter of #render directive must be an assignment expression", location);
}
}
}
@ -107,62 +108,68 @@ public class RenderDirective extends Directive {
}
String subFileName = Include.getSubFileName((String)value, parentFileName);
StatInfo statInfo = statInfoCache.get(subFileName);
if (statInfo == null) {
statInfo = parseStatInfo(env, subFileName);
statInfoCache.put(subFileName, statInfo);
} else if (env.getEngineConfig().isDevMode()) {
// statInfo.env.isSourceListModified() 逻辑可以支持 #render 子模板中的 #include 过来的子模板在 devMode 下在修改后可被重加载
if (statInfo.source.isModified() || statInfo.env.isSourceListModified()) {
statInfo = parseStatInfo(env, subFileName);
statInfoCache.put(subFileName, statInfo);
SubStat subStat = subStatCache.get(subFileName);
if (subStat == null) {
subStat = parseSubStat(env, subFileName);
subStatCache.put(subFileName, subStat);
} else if (env.isDevMode()) {
// subStat.env.isSourceListModified() 逻辑可以支持 #render 子模板中的 #include 过来的子模板在 devMode 下在修改后可被重加载
if (subStat.source.isModified() || subStat.env.isSourceListModified()) {
subStat = parseSubStat(env, subFileName);
subStatCache.put(subFileName, subStat);
}
}
statInfo.stat.exec(statInfo.env, scope, writer);
subStat.exec(null, scope, writer); // subStat.stat.exec(subStat.env, scope, writer);
scope.getCtrl().setJumpNone();
}
private StatInfo parseStatInfo(Env env, String subFileName) {
private SubStat parseSubStat(Env env, String subFileName) {
EngineConfig config = env.getEngineConfig();
// FileSource fileSource = new FileSource(config.getBaseTemplatePath(), subFileName, config.getEncoding());
ISource fileSource = config.getSourceFactory().getSource(config.getBaseTemplatePath(), subFileName, config.getEncoding());
// FileSource subFileSource = new FileSource(config.getBaseTemplatePath(), subFileName, config.getEncoding());
ISource subFileSource = config.getSourceFactory().getSource(config.getBaseTemplatePath(), subFileName, config.getEncoding());
try {
EnvSub envSub = new EnvSub(env);
Stat stat = new Parser(envSub, fileSource.getContent(), subFileName).parse();
return new StatInfo(envSub, stat, fileSource);
SubEnv subEnv = new SubEnv(env);
StatList subStatList = new Parser(subEnv, subFileSource.getContent(), subFileName).parse();
return new SubStat(subEnv, subStatList.getActualStat(), subFileSource);
} catch (Exception e) {
throw new ParseException(e.getMessage(), location, e);
}
}
private static class StatInfo {
EnvSub env;
Stat stat;
ISource source;
public static class SubStat extends Stat {
public SubEnv env;
public Stat stat;
public ISource source;
StatInfo(EnvSub env, Stat stat, ISource source) {
public SubStat(SubEnv env, Stat stat, ISource source) {
this.env = env;
this.stat = stat;
this.source = source;
}
@Override
public void exec(Env env, Scope scope, Writer writer) {
stat.exec(this.env, scope, writer);
}
}
/**
* EnvSub 用于将子模板与父模板中的模板函数隔离开来
* SubEnv 用于将子模板与父模板中的模板函数隔离开来
* 否则在子模板被修改并被重新解析时会再次添加子模板中的
* 模板函数从而抛出异常
*
* EnvSub 也可以使子模板中定义的模板函数不与上层产生冲突
* SubEnv 也可以使子模板中定义的模板函数不与上层产生冲突
* 有利于动态型模板渲染的模块化
*
* 注意 #render 子模板中定义的模板函数无法被上层调用
* 注意 #render 子模板中定义的模板函数无法在父模板中调用
*/
private static class EnvSub extends Env {
Env parentEnv;
public static class SubEnv extends Env {
public Env parentEnv;
public EnvSub(Env parentEnv) {
public SubEnv(Env parentEnv) {
super(parentEnv.getEngineConfig());
this.parentEnv = parentEnv;
}
@ -170,6 +177,7 @@ public class RenderDirective extends Directive {
/**
* 接管父类 getFunction()先从子模板中找模板函数找不到再去父模板中找
*/
@Override
public Define getFunction(String functionName) {
Define func = functionMap.get(functionName);
return func != null ? func : parentEnv.getFunction(functionName);

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -16,10 +16,11 @@
package com.jfinal.template.ext.directive;
import java.io.Writer;
import com.jfinal.template.Directive;
import com.jfinal.template.Env;
import com.jfinal.template.FastStringWriter;
import com.jfinal.template.io.CharWriter;
import com.jfinal.template.io.FastStringWriter;
import com.jfinal.template.io.Writer;
import com.jfinal.template.expr.ast.Const;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
@ -68,8 +69,14 @@ public class StringDirective extends Directive {
}
public void exec(Env env, Scope scope, Writer writer) {
CharWriter charWriter = new CharWriter(64);
FastStringWriter fsw = new FastStringWriter();
stat.exec(env, scope, fsw);
charWriter.init(fsw);
try {
stat.exec(env, scope, charWriter);
} finally {
charWriter.close();
}
if (this.isLocalAssignment) {
scope.setLocal(name, fsw.toString());

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -43,6 +43,14 @@ public class ByteExt {
public Double toDouble(Byte self) {
return self.doubleValue();
}
public Short toShort(Byte self) {
return self.shortValue();
}
public Byte toByte(Byte self) {
return self;
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -43,6 +43,14 @@ public class DoubleExt {
public Double toDouble(Double self) {
return self;
}
public Short toShort(Double self) {
return self.shortValue();
}
public Byte toByte(Double self) {
return self.byteValue();
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -43,6 +43,14 @@ public class FloatExt {
public Double toDouble(Float self) {
return self.doubleValue();
}
public Short toShort(Float self) {
return self.shortValue();
}
public Byte toByte(Float self) {
return self.byteValue();
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -63,6 +63,14 @@ public class IntegerExt {
public Double toDouble(Integer self) {
return self.doubleValue();
}
public Short toShort(Integer self) {
return self.shortValue();
}
public Byte toByte(Integer self) {
return self.byteValue();
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -43,6 +43,14 @@ public class LongExt {
public Double toDouble(Long self) {
return self.doubleValue();
}
public Short toShort(Long self) {
return self.shortValue();
}
public Byte toByte(Long self) {
return self.byteValue();
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -43,6 +43,14 @@ public class ShortExt {
public Double toDouble(Short self) {
return self.doubleValue();
}
public Short toShort(Short self) {
return self;
}
public Byte toByte(Short self) {
return self.byteValue();
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -79,6 +79,14 @@ public class StringExt {
public Double toDouble(String self) {
return StrKit.isBlank(self) ? null : Double.parseDouble(self);
}
public Short toShort(String self) {
return StrKit.isBlank(self) ? null : Short.parseShort(self);
}
public Byte toByte(String self) {
return StrKit.isBlank(self) ? null : Byte.parseByte(self);
}
}

View File

@ -0,0 +1,81 @@
/**
* 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.ext.sharedmethod;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
/**
* SharedMethodLib 共享方法库逐步添加一些最常用的共享方法
*
* <br>
* 3.3 版本之前的 Logic.isTrue(Object) 方法不再对 Collection
* Map数组IteratorIterable 进行为空的判断这部分逻辑已转移至
* SharedMethodLib.isEmpty(Object)
*/
public class SharedMethodLib {
/**
* 判断 CollectionMap数组IteratorIterable 类型对象中的元素个数是否为 0
* 规则
* 1null 返回 true
* 2ListSet 等一切继承自 Collection 返回 isEmpty()
* 3Map 返回 isEmpty()
* 4数组返回 length == 0
* 5Iterator 返回 ! hasNext()
* 6Iterable 返回 ! iterator().hasNext()
*
* 注意原先 Logic.isTrue(Object) 中对集合与数组类型为空的判断转移到此方法中
*/
public Boolean isEmpty(Object v) {
if (v == null) {
return true;
}
if (v instanceof Collection) {
return ((Collection<?>)v).isEmpty();
}
if (v instanceof Map) {
return ((Map<?, ?>)v).isEmpty();
}
if (v.getClass().isArray()) {
return Array.getLength(v) == 0;
}
if (v instanceof Iterator) {
return ! ((Iterator<?>)v).hasNext();
}
if (v instanceof Iterable) {
return ! ((Iterable<?>)v).iterator().hasNext();
}
throw new IllegalArgumentException("isEmpty(...) 方法只能接受 Collection、Map、数组、Iterator、Iterable 类型参数");
}
public Boolean notEmpty(Object v) {
return !isEmpty(v);
}
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -16,7 +16,8 @@
package com.jfinal.template.ext.spring;
import java.io.Writer;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
@ -56,105 +57,120 @@ public class JFinalView extends AbstractTemplateView {
}
}
Writer writer = response.getWriter();
JFinalViewResolver.engine.getTemplate(getUrl()).render(model, writer);
writer.flush();
try {
OutputStream os = response.getOutputStream();
JFinalViewResolver.engine.getTemplate(getUrl()).render(model, os);
} catch (Exception e) { // 捕获 ByteWriter.close() 抛出的 RuntimeException
Throwable cause = e.getCause();
if (cause instanceof IOException) { // ClientAbortExceptionEofException 直接或间接继承自 IOException
String name = cause.getClass().getSimpleName();
if ("ClientAbortException".equals(name) || "EofException".equals(name)) {
return ;
}
}
throw e;
}
}
}
@SuppressWarnings({"unchecked", "rawtypes", "deprecation"})
class InnerSession extends HashMap<Object, Object> implements HttpSession {
private static final long serialVersionUID = -8679493647540628009L;
private HttpSession session;
public InnerSession(HttpSession session) {
this.session = session;
}
// HashMap 相关方法处理 ----------------------------------------------------
/**
* 覆盖 HashMap put
*/
public Object put(Object name, Object value) {
session.setAttribute((String)name, value);
return null;
}
/**
* 覆盖 HashMap get
*/
public Object get(Object name) {
return session.getAttribute((String)name);
}
// Session 相关方法处理 ----------------------------------------------------
public Object getAttribute(String key) {
return session.getAttribute(key);
}
public Enumeration getAttributeNames() {
return session.getAttributeNames();
}
public long getCreationTime() {
return session.getCreationTime();
}
public String getId() {
return session.getId();
}
public long getLastAccessedTime() {
return session.getLastAccessedTime();
}
public int getMaxInactiveInterval() {
return session.getMaxInactiveInterval();
}
public ServletContext getServletContext() {
return session.getServletContext();
}
public javax.servlet.http.HttpSessionContext getSessionContext() {
return session.getSessionContext();
}
public Object getValue(String key) {
return session.getValue(key);
}
public String[] getValueNames() {
return session.getValueNames();
}
public void invalidate() {
session.invalidate();
}
public boolean isNew() {
return session.isNew();
}
public void putValue(String key, Object value) {
session.putValue(key, value);
}
public void removeAttribute(String key) {
session.removeAttribute(key);
}
public void removeValue(String key) {
session.removeValue(key);
}
public void setAttribute(String key, Object value) {
session.setAttribute(key, value);
}
public void setMaxInactiveInterval(int maxInactiveInterval) {
session.setMaxInactiveInterval(maxInactiveInterval);
@SuppressWarnings({"unchecked", "rawtypes", "deprecation"})
public static class InnerSession extends HashMap<Object, Object> implements HttpSession {
private static final long serialVersionUID = -8679493647540628009L;
private HttpSession session;
public InnerSession(HttpSession session) {
this.session = session;
}
// HashMap 相关方法处理 ----------------------------------------------------
/**
* 覆盖 HashMap put
*/
public Object put(Object name, Object value) {
session.setAttribute((String)name, value);
return null;
}
/**
* 覆盖 HashMap get
*/
public Object get(Object name) {
return session.getAttribute((String)name);
}
// Session 相关方法处理 ----------------------------------------------------
public Object getAttribute(String key) {
return session.getAttribute(key);
}
public Enumeration getAttributeNames() {
return session.getAttributeNames();
}
public long getCreationTime() {
return session.getCreationTime();
}
public String getId() {
return session.getId();
}
public long getLastAccessedTime() {
return session.getLastAccessedTime();
}
public int getMaxInactiveInterval() {
return session.getMaxInactiveInterval();
}
public ServletContext getServletContext() {
return session.getServletContext();
}
public javax.servlet.http.HttpSessionContext getSessionContext() {
return session.getSessionContext();
}
public Object getValue(String key) {
return session.getValue(key);
}
public String[] getValueNames() {
return session.getValueNames();
}
public void invalidate() {
session.invalidate();
}
public boolean isNew() {
return session.isNew();
}
public void putValue(String key, Object value) {
session.putValue(key, value);
}
public void removeAttribute(String key) {
session.removeAttribute(key);
}
public void removeValue(String key) {
session.removeValue(key);
}
public void setAttribute(String key, Object value) {
session.setAttribute(key, value);
}
public void setMaxInactiveInterval(int maxInactiveInterval) {
session.setMaxInactiveInterval(maxInactiveInterval);
}
public String toString() {
return session != null ? session.toString() : "null";
}
}
}
@ -163,4 +179,3 @@ class InnerSession extends HashMap<Object, Object> implements HttpSession {

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
* 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.
@ -18,7 +18,9 @@ package com.jfinal.template.ext.spring;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import javax.servlet.ServletContext;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
import com.jfinal.kit.StrKit;
import com.jfinal.template.Directive;
@ -54,6 +56,15 @@ public class JFinalViewResolver extends AbstractTemplateViewResolver {
static boolean sessionInView = false;
static boolean createSession = true;
private static JFinalViewResolver me = null;
/**
* me 会保存在第一次被创建对象
*/
public static JFinalViewResolver me() {
return me;
}
public Engine getEngine() {
return engine;
}
@ -83,6 +94,24 @@ public class JFinalViewResolver extends AbstractTemplateViewResolver {
}
}
/**
* 通过 List 配置多个 shared function file
* <pre>
* 配置示例
* <property name="sharedFunctionList">
* <list>
* <value>_layout.html</value>
* <value>_paginate.html</value>
* </list>
* </property>
* </pre>
*/
public void setSharedFunctionList(List<String> sharedFunctionList) {
if (sharedFunctionList != null) {
JFinalViewResolver.sharedFunctionFiles.addAll(sharedFunctionList);
}
}
/**
* 添加 shared function 文件可调用多次添加多个文件
*/
@ -94,8 +123,16 @@ public class JFinalViewResolver extends AbstractTemplateViewResolver {
/**
* 添加自定义指令
*/
public void addDirective(String directiveName, Class<? extends Directive> directiveClass) {
engine.addDirective(directiveName, directiveClass);
}
/**
* 添加自定义指令已被 addDirective(String, Class<? extends Directive>) 方法取代
*/
@Deprecated
public void addDirective(String directiveName, Directive directive) {
engine.addDirective(directiveName, directive);
addDirective(directiveName, directive.getClass());
}
/**
@ -157,6 +194,14 @@ public class JFinalViewResolver extends AbstractTemplateViewResolver {
engine.setSourceFactory(sourceFactory);
}
/**
* 设置为 ClassPathSourceFactory 的快捷方法
* ClassPathSourceFactory 将从 CLASSPATH jar 包中读取模板
*/
public void setToClassPathSourceFactory() {
engine.setToClassPathSourceFactory();
}
/**
* 设置模板基础路径
*/
@ -196,6 +241,12 @@ public class JFinalViewResolver extends AbstractTemplateViewResolver {
// ---------------------------------------------------------------
public JFinalViewResolver() {
synchronized(JFinalViewResolver.class) {
if (me == null) {
me = this;
}
}
setViewClass(requiredViewClass());
setOrder(0);
setContentType("text/html;charset=UTF-8");
@ -208,12 +259,30 @@ public class JFinalViewResolver extends AbstractTemplateViewResolver {
return JFinalView.class;
}
/**
* 支持 jfinal enjoyjspfreemarkervelocity 四类模板共存于一个项目中
*
* 注意这里采用识别 ".jsp"".ftl"".vm" 模板后缀名的方式来实现功能
* 所以 jfinal enjoy 模板不要采用上述三种后缀名否则功能将失效
* 还要注意与 jspfreemarkervelocity 以外类型模板共存使用时
* 需要改造该方法
*/
protected View loadView(String viewName, Locale locale) throws Exception {
String suffix = getSuffix();
if (".jsp".equals(suffix) || ".ftl".equals(suffix) || ".vm".equals(suffix)) {
return null;
} else {
return super.loadView(viewName, locale);
}
}
/**
* spring 回调利用 ServletContext 做必要的初始化工作
*/
@Override
protected void initServletContext(ServletContext servletContext) {
super.initServletContext(servletContext);
super.setExposeRequestAttributes(true);
initBaseTemplatePath(servletContext);
initSharedFunction();

View File

@ -0,0 +1,118 @@
/**
* 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;
import java.io.IOException;
import java.io.OutputStream;
/**
* ByteWriter
*/
public class ByteWriter extends Writer {
OutputStream out;
Encoder encoder;
char[] chars;
byte[] bytes;
public ByteWriter(Encoder encoder, int bufferSize) {
this.encoder = encoder;
this.chars = new char[bufferSize];
this.bytes = new byte[bufferSize * ((int)encoder.maxBytesPerChar())];
}
public ByteWriter init(OutputStream outputStream) {
this.out = outputStream;
return this;
}
public void flush() throws IOException {
out.flush();
}
public void close() {
out = null;
}
public void write(String str, int offset, int len) throws IOException {
int size, byteLen;
while (len > 0) {
size = (len > chars.length ? chars.length : len);
str.getChars(offset, offset + size, chars, 0);
byteLen = encoder.encode(chars, 0, size, bytes);
out.write(bytes, 0, byteLen);
offset += size;
len -= size;
}
}
public void write(String str) throws IOException {
write(str, 0, str.length());
}
public void write(StringBuilder stringBuilder, int offset, int len) throws IOException {
int size, byteLen;
while (len > 0) {
size = (len > chars.length ? chars.length : len);
stringBuilder.getChars(offset, offset + size, chars, 0);
byteLen = encoder.encode(chars, 0, size, bytes);
out.write(bytes, 0, byteLen);
offset += size;
len -= size;
}
}
public void write(StringBuilder stringBuilder) throws IOException {
write(stringBuilder, 0, stringBuilder.length());
}
public void write(IWritable writable) throws IOException {
byte[] data = writable.getBytes();
out.write(data, 0, data.length);
}
public void write(int intValue) throws IOException {
IntegerWriter.write(this, intValue);
}
public void write(long longValue) throws IOException {
LongWriter.write(this, longValue);
}
public void write(double doubleValue) throws IOException {
FloatingWriter.write(this, doubleValue);
}
public void write(float floatValue) throws IOException {
FloatingWriter.write(this, floatValue);
}
private static final byte[] TRUE_BYTES = "true".getBytes();
private static final byte[] FALSE_BYTES = "false".getBytes();
public void write(boolean booleanValue) throws IOException {
out.write(booleanValue ? TRUE_BYTES : FALSE_BYTES);
}
}

View File

@ -0,0 +1,113 @@
/**
* 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;
import java.io.IOException;
/**
* CharWriter
*/
public class CharWriter extends Writer {
java.io.Writer out;
char[] chars;
public CharWriter(int bufferSize) {
this.chars = new char[bufferSize];
}
public CharWriter init(java.io.Writer writer) {
this.out = writer;
return this;
}
public void flush() throws IOException {
out.flush();
}
public void close() {
out = null;
}
public void write(String str, int offset, int len) throws IOException {
int size;
while (len > 0) {
size = (len > chars.length ? chars.length : len);
str.getChars(offset, offset + size, chars, 0);
out.write(chars, 0, size);
offset += size;
len -= size;
}
}
public void write(String str) throws IOException {
write(str, 0, str.length());
}
public void write(StringBuilder stringBuilder, int offset, int len) throws IOException {
int size;
while (len > 0) {
size = (len > chars.length ? chars.length : len);
stringBuilder.getChars(offset, offset + size, chars, 0);
out.write(chars, 0, size);
offset += size;
len -= size;
}
}
public void write(StringBuilder stringBuilder) throws IOException {
write(stringBuilder, 0, stringBuilder.length());
}
public void write(IWritable writable) throws IOException {
char[] data = writable.getChars();
out.write(data, 0, data.length);
}
public void write(int intValue) throws IOException {
IntegerWriter.write(this, intValue);
}
public void write(long longValue) throws IOException {
LongWriter.write(this, longValue);
}
public void write(double doubleValue) throws IOException {
FloatingWriter.write(this, doubleValue);
}
public void write(float floatValue) throws IOException {
FloatingWriter.write(this, floatValue);
}
private static final char[] TRUE_CHARS = "true".toCharArray();
private static final char[] FALSE_CHARS = "false".toCharArray();
public void write(boolean booleanValue) throws IOException {
out.write(booleanValue ? TRUE_CHARS : FALSE_CHARS);
}
}

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2011-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;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
/**
* DateFormats
*/
public class DateFormats {
private Map<String, SimpleDateFormat> map = new HashMap<String, SimpleDateFormat>(16, 0.25F);
public SimpleDateFormat getDateFormat(String datePattern) {
SimpleDateFormat ret = map.get(datePattern);
if (ret == null) {
ret = new SimpleDateFormat(datePattern);
map.put(datePattern, ret);
}
return ret;
}
}

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) 2011-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;
/**
* Encoder
*/
public abstract class Encoder {
public abstract float maxBytesPerChar();
public abstract int encode(char[] chars, int offset, int len, byte[] bytes);
}

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2011-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;
import java.nio.charset.Charset;
import com.jfinal.template.EngineConfig;
/**
* EncoderFactory
*/
public class EncoderFactory {
protected Charset charset = Charset.forName(EngineConfig.DEFAULT_ENCODING);
void setEncoding(String encoding) {
charset = Charset.forName(encoding);
}
public Encoder getEncoder() {
if (Charset.forName("UTF-8").equals(charset)) {
return Utf8Encoder.me;
} else {
return new JdkEncoder(charset);
}
}
}

View File

@ -0,0 +1,171 @@
/**
* 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;
import java.io.IOException;
import java.io.Writer;
/**
* FastStringWriter
*
* <pre>
* JDK Writer 改造而来在其基础之上做了如下改变
* 1添加 char[] value 直接保存 char
* 2添加 int len 记录数据长度
* 3去掉 synchronized 操作
* 4添加 MAX_BUFFER_SIZE限定 value 被重用的最大长度
* 5去掉了 close() 方法声明中的 throws IOException并添加缓存回收逻辑
* </pre>
*/
public class FastStringWriter extends Writer {
private char[] value;
private int len;
private static int MAX_BUFFER_SIZE = 1024 * 256; // 1024 * 64;
public static void setMaxBufferSize(int maxBufferSize) {
int min = 256;
if (maxBufferSize < min) {
throw new IllegalArgumentException("maxBufferSize must more than " + min);
}
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 {
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,67 @@
/**
* 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;
import java.io.IOException;
/**
* FloatingWriter
*/
public class FloatingWriter {
public static void write(ByteWriter byteWriter, double doubleValue) throws IOException {
FloatingDecimal fd = new FloatingDecimal(doubleValue);
char[] chars = byteWriter.chars;
byte[] bytes = byteWriter.bytes;
int len = fd.getChars(chars);
for (int i=0; i<len; i++) {
bytes[i] = (byte)chars[i];
}
byteWriter.out.write(bytes, 0, len);
}
public static void write(ByteWriter byteWriter, float floatValue) throws IOException {
FloatingDecimal fd = new FloatingDecimal(floatValue);
char[] chars = byteWriter.chars;
byte[] bytes = byteWriter.bytes;
int len = fd.getChars(chars);
for (int i=0; i<len; i++) {
bytes[i] = (byte)chars[i];
}
byteWriter.out.write(bytes, 0, len);
}
public static void write(CharWriter charWriter, double doubleValue) throws IOException {
FloatingDecimal fd = new FloatingDecimal(doubleValue);
char[] chars = charWriter.chars;
int len = fd.getChars(chars);
charWriter.out.write(chars, 0, len);
}
public static void write(CharWriter charWriter, float floatValue) throws IOException {
FloatingDecimal fd = new FloatingDecimal(floatValue);
char[] chars = charWriter.chars;
int len = fd.getChars(chars);
charWriter.out.write(chars, 0, len);
}
}

View File

@ -0,0 +1,38 @@
/**
* Copyright (c) 2011-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;
/**
* IWritable 支持 OutputStreamWriter 双模式动态切换输出
*
* 详见 com.jfinal.template.stat.ast.Text 中的用法
*/
public interface IWritable {
/**
* OutputStream 模式下的 ByteWrite 使用
*/
public byte[] getBytes();
/**
* Writer 模式下的 CharWrite 使用
*/
public char[] getChars();
}

View File

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

View File

@ -0,0 +1,65 @@
/**
* Copyright (c) 2011-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;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
/**
* JdkEncoder
*/
public class JdkEncoder extends Encoder {
private CharsetEncoder ce;
public JdkEncoder(Charset charset) {
this.ce = charset.newEncoder();
}
public float maxBytesPerChar() {
return ce.maxBytesPerChar();
}
public int encode(char[] chars, int offset, int len, byte[] bytes) {
ce.reset();
ByteBuffer bb = ByteBuffer.wrap(bytes);
CharBuffer cb = CharBuffer.wrap(chars, offset, len);
try {
CoderResult cr = ce.encode(cb, bb, true);
if (!cr.isUnderflow())
cr.throwException();
cr = ce.flush(bb);
if (!cr.isUnderflow())
cr.throwException();
return bb.position();
} catch (CharacterCodingException x) {
// Substitution is always enabled,
// so this shouldn't happen
throw new RuntimeException("Encode error: " + x.getMessage(), x);
}
}
}

View File

@ -0,0 +1,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

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

View File

@ -0,0 +1,102 @@
/**
* 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;
// import java.nio.charset.MalformedInputException;
/**
* Utf8Encoder
*
* http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/sun/nio/cs/UTF_8.java?av=f
* http://grepcode.com/search?query=ArrayEncoder&start=0&entity=type&n=
*/
public class Utf8Encoder extends Encoder {
public static final Utf8Encoder me = new Utf8Encoder();
public float maxBytesPerChar() {
return 3.0F;
}
public int encode(char[] chars, int offset, int len, byte[] bytes) {
int sl = offset + len;
int dp = 0;
int dlASCII = dp + Math.min(len, bytes.length);
// ASCII only optimized loop
while (dp < dlASCII && chars[offset] < '\u0080') {
bytes[dp++] = (byte) chars[offset++];
}
while (offset < sl) {
char c = chars[offset++];
if (c < 0x80) {
// Have at most seven bits
bytes[dp++] = (byte) c;
} else if (c < 0x800) {
// 2 bytes, 11 bits
bytes[dp++] = (byte) (0xc0 | (c >> 6));
bytes[dp++] = (byte) (0x80 | (c & 0x3f));
} else if (c >= '\uD800' && c < ('\uDFFF' + 1)) { //Character.isSurrogate(c) but 1.7
final int uc;
int ip = offset - 1;
if (Character.isHighSurrogate(c)) {
if (sl - ip < 2) {
uc = -1;
} else {
char d = chars[ip + 1];
if (Character.isLowSurrogate(d)) {
uc = Character.toCodePoint(c, d);
} else {
// throw new RuntimeException("encode UTF8 error", new MalformedInputException(1));
bytes[dp++] = (byte) '?';
continue;
}
}
} else {
if (Character.isLowSurrogate(c)) {
// throw new RuntimeException("encode UTF8 error", new MalformedInputException(1));
bytes[dp++] = (byte) '?';
continue;
} else {
uc = c;
}
}
if (uc < 0) {
bytes[dp++] = (byte) '?';
} else {
bytes[dp++] = (byte) (0xf0 | ((uc >> 18)));
bytes[dp++] = (byte) (0x80 | ((uc >> 12) & 0x3f));
bytes[dp++] = (byte) (0x80 | ((uc >> 6) & 0x3f));
bytes[dp++] = (byte) (0x80 | (uc & 0x3f));
offset++; // 2 chars
}
} else {
// 3 bytes, 16 bits
bytes[dp++] = (byte) (0xe0 | ((c >> 12)));
bytes[dp++] = (byte) (0x80 | ((c >> 6) & 0x3f));
bytes[dp++] = (byte) (0x80 | (c & 0x3f));
}
}
return dp;
}
}

View File

@ -0,0 +1,68 @@
/**
* Copyright (c) 2011-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;
import java.io.IOException;
import java.util.Date;
/**
* Writer
*/
public abstract class Writer {
protected DateFormats formats = new DateFormats();
public abstract void flush() throws IOException;
public abstract void close();
public abstract void write(IWritable writable) throws IOException;
public abstract void write(String string, int offset, int length) throws IOException;
public abstract void write(String string) throws IOException;
public abstract void write(StringBuilder stringBuilder, int offset, int length) throws IOException;
public abstract void write(StringBuilder stringBuilder) throws IOException;
public abstract void write(boolean booleanValue) throws IOException;
public abstract void write(int intValue) throws IOException;
public abstract void write(long longValue) throws IOException;
public abstract void write(double doubleValue) throws IOException;
public abstract void write(float floatValue) throws IOException;
public void write(short shortValue) throws IOException {
write((int)shortValue);
}
public void write(Date date, String datePattern) throws IOException {
String str = formats.getDateFormat(datePattern).format(date);
write(str, 0, str.length());
}
}

View File

@ -0,0 +1,86 @@
/**
* Copyright (c) 2011-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;
/**
* WriterBuffer
*/
public class WriterBuffer {
private static final int MIN_BUFFER_SIZE = 64; // 缓冲区最小 64 字节
private static final int MAX_BUFFER_SIZE = 1024 * 1024 * 10; // 缓冲区最大 10M 字节
private int bufferSize = 2048; // 缓冲区大小
private EncoderFactory encoderFactory = new EncoderFactory();
private final ThreadLocal<ByteWriter> byteWriters = new ThreadLocal<ByteWriter>() {
protected ByteWriter initialValue() {
return new ByteWriter(encoderFactory.getEncoder(), bufferSize);
}
};
private final ThreadLocal<CharWriter> charWriters = new ThreadLocal<CharWriter>() {
protected CharWriter initialValue() {
return new CharWriter(bufferSize);
}
};
private final ThreadLocal<FastStringWriter> fastStringWriters = new ThreadLocal<FastStringWriter>() {
protected FastStringWriter initialValue() {
return new FastStringWriter();
}
};
public ByteWriter getByteWriter(java.io.OutputStream outputStream) {
return byteWriters.get().init(outputStream);
}
public CharWriter getCharWriter(java.io.Writer writer) {
return charWriters.get().init(writer);
}
public FastStringWriter getFastStringWriter() {
return fastStringWriters.get();
}
public void setBufferSize(int bufferSize) {
if (bufferSize < MIN_BUFFER_SIZE || bufferSize > MAX_BUFFER_SIZE) {
throw new IllegalArgumentException("bufferSize must between " + (MIN_BUFFER_SIZE-1) + " and " + (MAX_BUFFER_SIZE+1));
}
this.bufferSize = bufferSize;
}
public void setEncoderFactory(EncoderFactory encoderFactory) {
if (encoderFactory == null) {
throw new IllegalArgumentException("encoderFactory can not be null");
}
this.encoderFactory = encoderFactory;
}
public void setEncoding(String encoding) {
encoderFactory.setEncoding(encoding);
}
}

Some files were not shown because too many files have changed in this diff Show More