32 Commits

Author SHA1 Message Date
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
38 changed files with 1399 additions and 324 deletions

3
.gitignore vendored
View File

@@ -51,3 +51,6 @@ a_little_config_pro.txt
dev_plan.txt

View File

@@ -1,16 +1,26 @@
### Enjoy
Enjoy 是基于 Java 语言的极轻量极魔板引擎。极轻量级仅 171K 并且不依赖任何第三方。极简设计仅 if、for、set、define、include、render 个核心指令,让学习成本低到极致。独创 DKFF(Dynamic Key Feature Forward) 词法分析算法与 DLRD (Double Layer Recursive Descent)语法分析算法,避免使用 javacc、antlr、jflex 生成器,令代码量少到极致。
Enjoy 是基于 Java 语言的极轻量极魔板引擎。极轻量级仅 227 KB 并且不依赖任何第三方。极简设计仅 if、for、switch、set、define、include、render 个核心指令,让学习成本低到极致。独创 DKFF(Dynamic Key Feature Forward) 词法分析算法与 DLRD (Double Layer Recursive Descent)语法分析算法,避免使用 javacc、antlr、jflex 生成器,令代码量少到极致。
#### Maven 坐标
```java
<dependency>
<groupId>com.jfinal</groupId>
<artifactId>enjoy</artifactId>
<version>4.3</version>
</dependency>
```
#### Enjoy 主要特点
- 消灭传统模板引擎中大量繁杂概念,仅个核心指令,学习成本极低
- 独创 DKFF 词法分析算法与 DLRD 语法分析算法避免使用javacc、antlr
- 功能强大,极为简单覆盖掉 freemarker、velocity 的核心功能
- 扩展性强,支持多种扩展方式,且是唯一支持指令级扩展的模板引擎
- 消灭传统模板引擎中大量繁杂概念,仅个核心指令,学习成本极低
- 独创 DKFF 词法分析算法与 DLRD 语法分析算法,避免使用 javacc、antlr
- 与 java 打通式设计,在模板中与 java 交互极为方便
- 贴近 java 使用直觉,为 java 开发者量身打造
- 功能强大,极为简单覆盖掉 freemarker、velocity 的核心功能
- 扩展性强,支持多种扩展方式,且是唯一支持指令级扩展的模板引擎
- 回归模板引擎渲染 View 数据的本质,采用指令式设计,避免 view 层表达复杂逻辑
- 体积小,仅 171K,且不依赖于任何第三方
- 体积小,仅 227 KB,且不依赖于任何第三方
#### 简单示例:
@@ -34,9 +44,10 @@ Enjoy 是基于 Java 语言的极轻量极魔板引擎。极轻量级仅 171K
```
**2.详细使用方法见 jfinal 手册**
read me 正在补充,详细使用文档请下载 jfinal.com 官网的 jfinal 手册[http://www.jfinal.com](http://www.jfinal.com)
**JFinal 官方网站[http://www.jfinal.com](http://www.jfinal.com)**
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)**

View File

@@ -4,7 +4,7 @@
<artifactId>enjoy</artifactId>
<packaging>jar</packaging>
<name>enjoy</name>
<version>3.5</version>
<version>4.3</version>
<url>http://www.jfinal.com</url>
<description>Enjoy is a simple, light, rapid, independent, extensible Java Template Engine.</description>
@@ -73,8 +73,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
@@ -128,3 +128,5 @@
</plugins>
</build>
</project>

View File

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

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

@@ -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,238 @@
/**
* 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");
}
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

@@ -26,6 +26,7 @@ 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;
@@ -278,6 +279,11 @@ public class Engine {
return this;
}
public Engine removeSharedObject(String name) {
config.removeSharedObject(name);
return this;
}
/**
* Set output directive factory
*/
@@ -471,6 +477,17 @@ public class Engine {
return config.getEncoding();
}
/**
* Enjoy 模板引擎对 UTF-8 的 encoding 做过性能优化,某些偏门字符在
* 被编码为 UTF-8 时会出现异常,此时可以通过继承扩展 EncoderFactory
* 来解决编码异常,具体用法参考:
* http://www.jfinal.com/feedback/5340
*/
public Engine setEncoderFactory(EncoderFactory encoderFactory) {
config.setEncoderFactory(encoderFactory);
return this;
}
public Engine setWriterBufferSize(int bufferSize) {
config.setWriterBufferSize(bufferSize);
return this;
@@ -509,10 +526,10 @@ public class Engine {
*
* 系统当前默认 FieldGetter 实现类及其位置如下:
* GetterMethodFieldGetter ---> 调用 getter 方法取值
* RealFieldGetter ---> 直接获取 public 型的 object.field 值
* ModelFieldGetter ---> 调用 Model.get(String) 方法取值
* RecordFieldGetter ---> 调用 Record.get(String) 方法取值
* MapFieldGetter ---> 调用 Map.get(String) 方法取值
* RealFieldGetter ---> 直接获取 public 型的 object.field 值
* MapFieldGetter ---> 调用 Map.get(String) 方法取值
* ArrayLengthGetter ---> 获取数组长度
*
* 根据以上次序,如果要插入 IsMethodFieldGetter 到 GetterMethodFieldGetter
@@ -520,8 +537,8 @@ public class Engine {
* Engine.addFieldGetter(1, new IsMethodFieldGetter());
*
* 注IsMethodFieldGetter 系统已经提供,只是默认没有启用。该实现类通过调用
* target.isXxx() 方法获取 target.xxx 表达式的值,其中 xxx 字段必须是
* Boolean/boolean 类型
* target.isXxx() 方法获取 target.xxx 表达式的值,其中 isXxx() 返回值
* 必须是 Boolean/boolean 类型才会被调用
*/
public static void addFieldGetter(int index, FieldGetter fieldGetter) {
FieldKit.addFieldGetter(index, fieldGetter);
@@ -539,8 +556,19 @@ public class Engine {
FieldKit.removeFieldGetter(fieldGetterClass);
}
public static void setToFastFieldKeyBuilder() {
FieldKeyBuilder.setToFastFieldKeyBuilder();
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

@@ -73,9 +73,9 @@ public class EngineConfig {
addDirective("string", StringDirective.class);
addDirective("random", RandomDirective.class);
addDirective("number", NumberDirective.class);
addDirective("call", CallDirective.class);
// Add official shared method of Template Engine
// addSharedMethod(new Json());
addSharedMethod(new SharedMethodLib());
}
@@ -83,6 +83,7 @@ public class EngineConfig {
* 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);
@@ -208,10 +209,16 @@ public class EngineConfig {
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
*/
@@ -261,8 +268,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);
}
}

View File

@@ -17,7 +17,6 @@
package com.jfinal.template;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
@@ -109,7 +108,7 @@ public class Template {
public StringBuilder renderToStringBuilder(Map<?, ?> data) {
FastStringWriter fsw = new FastStringWriter();
render(data, fsw);
return fsw.getBuffer();
return fsw.toStringBuilder();
}
/**
@@ -117,16 +116,10 @@ public class Template {
* 适用于代码生成器类似应用场景
*/
public void render(Map<?, ?> data, File file) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
try (FileOutputStream fos = new FileOutputStream(file)) {
render(data, fos);
} catch (FileNotFoundException e) {
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fos != null) {
try {fos.close();} catch (IOException e) {e.printStackTrace(System.err);}
}
}
}

View File

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

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

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

@@ -28,10 +28,10 @@ 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 {
@@ -73,9 +73,7 @@ public class Field extends Expr {
if (fieldGetter.notNull()) {
return fieldGetter.get(target, fieldName);
}
} catch (TemplateException e) {
throw e;
} catch (ParseException e) {
} catch (TemplateException | ParseException e) {
throw e;
} catch (Exception e) {
throw new TemplateException(e.getMessage(), location, e);

View File

@@ -68,10 +68,10 @@ public class FieldGetters {
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.getParameterTypes().length == 0) {
if (MethodKit.isForbiddenMethod(getterName)) {
throw new RuntimeException("Forbidden method: " + getterName);
}
if (method.getName().equals(getterName) && method.getParameterCount() == 0) {
// if (MethodKit.isForbiddenMethod(getterName)) {
// throw new RuntimeException("Forbidden method: " + getterName);
// }
return new GetterMethodFieldGetter(method);
}
@@ -115,7 +115,7 @@ public class FieldGetters {
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.getParameterTypes().length == 0) {
if (method.getName().equals(isMethodName) && method.getParameterCount() == 0) {
Class<?> returnType = method.getReturnType();
if (returnType == Boolean.class || returnType == boolean.class) {
return new IsMethodFieldGetter(method);

View File

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

View File

@@ -44,10 +44,13 @@ public class FieldKit {
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());
ret.addLast(new RealFieldGetter(null));
// 挪到第二的位置addSharedObject(..., modelObj) 用法可以获取到 model 中的 public 属性
// ret.addLast(new RealFieldGetter(null));
ret.addLast(new ArrayLengthGetter());
// ret.addLast(new IsMethodFieldGetter());
@@ -133,6 +136,40 @@ public class FieldKit {
getters = ret.toArray(new FieldGetter[ret.size()]);
}
public static void clearCache() {
fieldGetterCache.clear();
}
/**
* 设置极速模式
*
* 极速模式将生成代理对象来消除 java.lang.reflect.Method.invoke(...) 调用,
* 性能提升 12.9%
*/
public static synchronized void setFastMode(boolean fastMode) {
if (fastMode) {
if ( !contains(FastFieldGetter.class) ) {
addFieldGetterToFirst(new FastFieldGetter());
}
} else {
if (contains(FastFieldGetter.class)) {
removeFieldGetter(FastFieldGetter.class);
}
}
}
/**
* 判断是否包含某个 FieldGetter
*/
public static boolean contains(Class<? extends FieldGetter> fieldGetterClass) {
for (FieldGetter fg : getters) {
if (fg.getClass() == fieldGetterClass) {
return true;
}
}
return false;
}
}

View File

@@ -88,9 +88,7 @@ public class Method extends Expr {
}
throw new TemplateException(buildMethodNotFoundSignature("public method not found: " + target.getClass().getName() + ".", methodName, argValues), location);
} catch (TemplateException e) {
throw e;
} catch (ParseException e) {
} catch (TemplateException | ParseException e) {
throw e;
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();

View File

@@ -48,7 +48,8 @@ public class MethodKit {
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
};
for (Class<?> c : cs) {
forbiddenClasses.add(c);
@@ -59,7 +60,7 @@ 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",
@@ -98,6 +99,10 @@ public class MethodKit {
forbiddenClasses.add(clazz);
}
public static void removeForbiddenClass(Class<?> clazz) {
forbiddenClasses.remove(clazz);
}
public static boolean isForbiddenMethod(String methodName) {
return forbiddenMethods.contains(methodName);
}
@@ -106,6 +111,14 @@ public class MethodKit {
forbiddenMethods.add(methodName);
}
public static void removeForbiddenMethod(String methodName) {
forbiddenMethods.remove(methodName);
}
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);

View File

@@ -63,9 +63,7 @@ public class SharedMethod extends Expr {
throw new TemplateException(Method.buildMethodNotFoundSignature("Shared method not found: ", methodName, argValues), location);
}
} catch (TemplateException e) {
throw e;
} catch (ParseException e) {
} catch (TemplateException | ParseException e) {
throw e;
} catch (Exception e) {
throw new TemplateException(e.getMessage(), location, e);

View File

@@ -72,9 +72,7 @@ public class StaticMethod extends Expr {
throw new TemplateException(Method.buildMethodNotFoundSignature("public static method not found: " + clazz.getName() + "::", methodName, argValues), location);
}
} catch (TemplateException e) {
throw e;
} catch (ParseException e) {
} catch (TemplateException | ParseException e) {
throw e;
} catch (Exception e) {
throw new TemplateException(e.getMessage(), location, e);

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为函数名p1、p2、pn 为被调用函数所使用的参数
*
*
* 如果希望模板函数不存在时忽略其调用,添加常量值 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

@@ -16,9 +16,12 @@
package com.jfinal.template.ext.directive;
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,45 +32,67 @@ 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;
private void escape(String str, Writer w) throws IOException {
int len = str.length();
if (len == 0) {
return ;
}
int len = str.length();
StringBuilder ret = new StringBuilder(len * 2);
for (int i = 0; i < len; i++) {
char cur = str.charAt(i);
switch (cur) {
case '<':
ret.append("&lt;");
w.write("&lt;");
break;
case '>':
ret.append("&gt;");
w.write("&gt;");
break;
case '"':
ret.append("&quot;");
w.write("&quot;");
break;
case '\'':
// ret.append("&apos;"); // IE 不支持 &apos; 考虑 &#39;
ret.append("&#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

@@ -46,15 +46,7 @@ public class ByteWriter extends Writer {
}
public void close() {
try {
if (out != null) {
out.flush();
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
out = null;
}
out = null;
}
public void write(String str, int offset, int len) throws IOException {

View File

@@ -40,15 +40,7 @@ public class CharWriter extends Writer {
}
public void close() {
try {
if (out != null) {
out.flush();
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
out = null;
}
out = null;
}
public void write(String str, int offset, int len) throws IOException {

View File

@@ -16,102 +16,153 @@
package com.jfinal.template.io;
import java.io.IOException;
import java.io.Writer;
/**
* FastStringWriter
*
* <pre>
* 由 JDK 中 StringWriter 改造而来,在其基础之上做了如下改变:
* 1StringBuffer 属性改为 StringBuilder避免了前者的 synchronized 操作
* 2添加了 MAX_SIZE 属性
* 3去掉了 close() 方法声明中的 throws IOException并添加了代码原先该方法中无任何代码
* 由 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 StringBuilder buf;
private char[] value;
private int len;
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() {
}
static int MAX_SIZE = 1024 * 64;
/**
* 由 StringWriter.close() 改造而来,原先该方法中无任何代码 ,改造如下:
* 1去掉 throws IOException
* 2添加 buf 空间释放处理逻辑
* 3添加 buf.setLength(0),以便于配合 ThreadLocal 回收利用
*/
public void close() {
if (buf.length() > MAX_SIZE) {
buf = new StringBuilder(MAX_SIZE / 2); // 释放空间占用过大的 buf
} else {
buf.setLength(0);
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 {
}
}

View File

@@ -18,7 +18,6 @@ package com.jfinal.template.source;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
@@ -64,7 +63,7 @@ public class ClassPathSource implements ISource {
this.classLoader = getClassLoader();
this.url = classLoader.getResource(finalFileName);
if (url == null) {
throw new IllegalArgumentException("File not found : \"" + finalFileName + "\"");
throw new IllegalArgumentException("File not found in CLASSPATH or JAR : \"" + finalFileName + "\"");
}
processIsInJarAndlastModified();
@@ -141,9 +140,8 @@ public class ClassPathSource implements ISource {
public static StringBuilder loadFile(InputStream inputStream, String encoding) {
StringBuilder ret = new StringBuilder();
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(inputStream, encoding));
try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, encoding))) {
// br = new BufferedReader(new FileReader(fileName));
String line = br.readLine();
if (line != null) {
@@ -159,16 +157,6 @@ public class ClassPathSource implements ISource {
} catch (Exception e) {
throw new RuntimeException(e);
}
finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
// com.jfinal.kit.LogKit.error(e.getMessage(), e);
e.printStackTrace();
}
}
}
}
public String toString() {

View File

@@ -19,7 +19,6 @@ package com.jfinal.template.source;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import com.jfinal.template.EngineConfig;
@@ -92,9 +91,8 @@ public class FileSource implements ISource {
public static StringBuilder loadFile(File file, String encoding) {
StringBuilder ret = new StringBuilder((int)file.length() + 3);
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream(file), encoding));
try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), encoding))) {
// br = new BufferedReader(new FileReader(fileName));
String line = br.readLine();
if (line != null) {
@@ -110,16 +108,6 @@ public class FileSource implements ISource {
} catch (Exception e) {
throw new RuntimeException(e);
}
finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
// com.jfinal.kit.LogKit.error(e.getMessage(), e);
e.printStackTrace();
}
}
}
}
public String toString() {

View File

@@ -24,22 +24,7 @@ import com.jfinal.template.expr.ExprParser;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.expr.ast.ForCtrl;
import com.jfinal.template.stat.Symbol;
import com.jfinal.template.stat.ast.Break;
import com.jfinal.template.stat.ast.Call;
import com.jfinal.template.stat.ast.Continue;
import com.jfinal.template.stat.ast.Define;
import com.jfinal.template.stat.ast.Else;
import com.jfinal.template.stat.ast.ElseIf;
import com.jfinal.template.stat.ast.For;
import com.jfinal.template.stat.ast.If;
import com.jfinal.template.stat.ast.Include;
import com.jfinal.template.stat.ast.Return;
import com.jfinal.template.stat.ast.Set;
import com.jfinal.template.stat.ast.SetGlobal;
import com.jfinal.template.stat.ast.SetLocal;
import com.jfinal.template.stat.ast.Stat;
import com.jfinal.template.stat.ast.StatList;
import com.jfinal.template.stat.ast.Text;
import com.jfinal.template.stat.ast.*;
/**
* DLRD (Double Layer Recursive Descent) Parser
@@ -227,7 +212,41 @@ public class Parser {
case ELSE:
case END:
case EOF:
case CASE:
case DEFAULT:
return null;
case SWITCH:
move();
para = matchPara(name);
Switch _switch = new Switch(parseExprList(para), getLocation(name.row));
CaseSetter currentCaseSetter = _switch;
for (Token currentToken=peek(); ; currentToken=peek()) {
if (currentToken.symbol == Symbol.CASE) {
move();
para = matchPara(currentToken);
statList = statList();
Case nextCase = new Case(parseExprList(para), statList, getLocation(currentToken.row));
currentCaseSetter.setNextCase(nextCase);
currentCaseSetter = nextCase;
} else if (currentToken.symbol == Symbol.DEFAULT) {
move();
statList = statList();
Default _default = new Default(statList);
_switch.setDefault(_default, getLocation(currentToken.row));
} else if (currentToken.symbol == Symbol.TEXT) {
TextToken tt = (TextToken)currentToken;
if (tt.getContent().toString().trim().length() != 0) {
throw new ParseException("Syntax error: expect #case or #default directive", getLocation(currentToken.row));
}
move();
} else {
break ;
}
}
matchEnd(name);
return _switch;
default :
throw new ParseException("Syntax error: can not match the token: " + name.value(), getLocation(name.row));
}

View File

@@ -45,6 +45,10 @@ enum Symbol {
BREAK("break", false),
RETURN("return", false),
SWITCH("switch", true),
CASE("case", true),
DEFAULT("default", false),
ID("ID", false), // 标识符:下划线或字母开头 ^[A-Za-z_][A-Za-z0-9_]*$
PARA("PARA", false),
@@ -68,6 +72,10 @@ enum Symbol {
put(Symbol.CONTINUE.getName(), CONTINUE);
put(Symbol.RETURN.getName(), RETURN);
put(Symbol.SWITCH.getName(), SWITCH);
put(Symbol.CASE.getName(), CASE);
put(Symbol.DEFAULT.getName(), DEFAULT);
put(Symbol.DEFINE.getName(), DEFINE);
put(Symbol.SET.getName(), SET);
put(Symbol.SET_LOCAL.getName(), SET_LOCAL);

View File

@@ -0,0 +1,89 @@
/**
* 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.stat.ast;
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.Location;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
/**
* Case
*/
public class Case extends Stat implements CaseSetter {
private Expr[] exprArray;
private Stat stat;
private Case nextCase;
public Case(ExprList exprList, StatList statList, Location location) {
if (exprList.length() == 0) {
throw new ParseException("The parameter of #case directive can not be blank", location);
}
this.exprArray = exprList.getExprArray();
this.stat = statList.getActualStat();
}
public void setNextCase(Case nextCase) {
this.nextCase = nextCase;
}
public void exec(Env env, Scope scope, Writer writer) {
throw new TemplateException("#case 指令的 exec 不能被调用", location);
}
boolean execIfMatch(Object switchValue, Env env, Scope scope, Writer writer) {
if (exprArray.length == 1) {
Object value = exprArray[0].eval(scope);
// 照顾 null == null 以及数值比较小的整型数据比较
if (value == switchValue) {
stat.exec(env, scope, writer);
return true;
}
if (value != null && value.equals(switchValue)) {
stat.exec(env, scope, writer);
return true;
}
} else {
for (Expr expr : exprArray) {
Object value = expr.eval(scope);
// 照顾 null == null 以及数值比较小的整型数据比较
if (value == switchValue) {
stat.exec(env, scope, writer);
return true;
}
if (value != null && value.equals(switchValue)) {
stat.exec(env, scope, writer);
return true;
}
}
}
return nextCase != null ? nextCase.execIfMatch(switchValue, env, scope, writer) : false;
}
}

View File

@@ -0,0 +1,24 @@
/**
* 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.stat.ast;
/**
* CaseSetter
*/
public interface CaseSetter {
public void setNextCase(Case nextCase);
}

View File

@@ -0,0 +1,39 @@
/**
* 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.stat.ast;
import com.jfinal.template.Env;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope;
/**
* Default
*
* #switch 指令内部的 #default 指令
*/
public class Default extends Stat {
private Stat stat;
public Default(StatList statList) {
this.stat = statList.getActualStat();
}
public void exec(Env env, Scope scope, Writer writer) {
stat.exec(env, scope, writer);
}
}

View File

@@ -69,7 +69,7 @@ public class Include extends Stat {
Expr expr = exprList.getExpr(0);
if (expr instanceof Const && ((Const)expr).isStr()) {
} else {
throw new ParseException("The first parameter of #include directive must be String", location);
throw new ParseException("The first parameter of #include directive must be String, or use the #render directive", location);
}
// 其它参数必须为赋值表达式
if (len > 1) {

View File

@@ -66,14 +66,10 @@ public class Output extends Stat {
} else {
writer.write(value.toString());
}
} else if (value instanceof Boolean) {
writer.write((Boolean)value);
} else if (value != null) {
writer.write(value.toString());
}
} catch (TemplateException e) {
throw e;
} catch (ParseException e) {
} catch(TemplateException | ParseException e) {
throw e;
} catch(Exception e) {
throw new TemplateException(e.getMessage(), location, e);

View File

@@ -0,0 +1,111 @@
/**
* 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.stat.ast;
import com.jfinal.template.Env;
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.Location;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
/**
* Switch
*
* #switch 指令与 Java 12 switch 新特性的设计相似: http://openjdk.java.net/jeps/325
*
* 在与 java 老版本指令基本用法相同的基础上,主要变化与特性有:
* 1: 移除 java 语法中的 fall-through semantics即不需要 break 关键字进行断开
* 2: 不引入 #break 指令,代码更少、更优雅
* 3: #case 参数可使用多个用逗号分隔的表达式,每个表达式求值后与 #switch 参数求值后比较,
* 从根本上消除了 #break 指令的必要性
* 4: #case 支持任意类型数据与表达式java 语言只支持少数常量类型)
*
* <pre>
* 示例:
* #switch (month)
* #case (1, 3, 5, 7, 8, 10, 12)
* #(month) 月有 31 天
* #case (2)
* #(month) 月平年有28天闰年有29天
* #default
* 月份错误: #(month ?? "null")
* #end
*
* 如上代码所示,如果 #case 指令参数有多个值,那么可以用逗号分隔,
* 上述逗号表达式的值 1, 3, 5, 7, 8, 10, 12 之中只要有一个与
* switch 指令参数 month 相等的话,该 case 分支就会被执行,
* 该特性从根本上消灭了 #break 指令的必要性
*
*
* 除了常量值以外 #case 参数还可以是任意表达式
* 例如:
* #case (a, b, c, x + y, obj.method(z))
*
* 上述代码中 #case 参数中的所有表达式先会被求值,然后逐一与 #switch
* 参数进行对比,同样也是只要有一个对比相等,则该 case 分支就会被执行
*
* </pre>
*/
public class Switch extends Stat implements CaseSetter {
private Expr expr;
private Case nextCase;
private Default _default;
public Switch(ExprList exprList, Location location) {
if (exprList.length() == 0) {
throw new ParseException("The parameter of #switch directive can not be blank", location);
}
this.expr = exprList.getActualExpr();
}
public void setNextCase(Case nextCase) {
this.nextCase = nextCase;
}
public void setDefault(Default _default, Location location) {
if (this._default != null) {
throw new ParseException("The #default case of #switch is already defined", location);
}
this._default = _default;
}
public void exec(Env env, Scope scope, Writer writer) {
Object switchValue = expr.eval(scope);
if (nextCase != null && nextCase.execIfMatch(switchValue, env, scope, writer)) {
return ;
}
if (_default != null) {
_default.exec(env, scope, writer);
}
}
public boolean hasEnd() {
return true;
}
}

View File

@@ -28,7 +28,3 @@ public class SpringBootConfig {
}
}