浏览代码

!128 增加LineNumberActionReporter和RenderElseDirective Enjoy模版指令
Merge pull request !128 from 山东小木/master

JFinal 1 年之前
父节点
当前提交
f5347e70aa

+ 82 - 0
src/main/java/com/jfinal/core/LineNumberActionReporter.java

@@ -0,0 +1,82 @@
+package com.jfinal.core;
+import com.jfinal.aop.Interceptor;
+import com.jfinal.core.paragetter.JsonRequest;
+import javassist.ClassPool;
+import javassist.CtMethod;
+import javassist.NotFoundException;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.util.Date;
+
+/**
+ * LineNumberActionReporter
+ * 输出真实行号
+ * 建议开发模式下使用(有性能损耗)
+ * @author 山东小木
+ */
+public class LineNumberActionReporter extends ActionReporter {
+    @Override
+    public void report(String target, Controller controller, Action action) {
+        CtMethod ctMethod = null;
+        int lineNumber = 1;
+        try {
+            ctMethod = ClassPool.getDefault().getMethod(controller.getClass().getName(), action.getMethodName());
+            lineNumber = ctMethod.getMethodInfo().getLineNumber(0);
+        } catch (NotFoundException e) {
+            e.printStackTrace();
+        }
+        StringBuilder sb = new StringBuilder(title).append(sdf.get().format(new Date())).append(" --------------------------\n");
+        sb.append("Url         : ").append(controller.getRequest().getMethod()).append(' ').append(target).append('\n');
+        Class<? extends Controller> cc = action.getControllerClass();
+        sb.append("Controller  : ").append(cc.getName()).append(".(").append(cc.getSimpleName()).append(".java:").append(lineNumber).append(")\n");
+        sb.append("Method      : ").append(action.getMethodName()).append('\n');
+
+        String urlParas = controller.getPara();
+        if (urlParas != null) {
+            sb.append("UrlPara     : ").append(urlParas).append('\n');
+        }
+
+        Interceptor[] inters = action.getInterceptors();
+        if (inters.length > 0) {
+            sb.append("Interceptor : ");
+            lineNumber = 1;
+            ctMethod = null;
+            Class<? extends Interceptor> ic;
+            for (int i=0; i<inters.length; i++) {
+                lineNumber = 1;
+                ctMethod = null;
+                if (i > 0) {
+                    sb.append("\n              ");
+                }
+                ic = inters[i].getClass();
+                try {
+                    ctMethod = ClassPool.getDefault().getMethod(ic.getName(), "intercept");
+                    lineNumber = ctMethod.getMethodInfo().getLineNumber(0);
+                } catch (NotFoundException e) {
+                    e.printStackTrace();
+                }
+                sb.append(ic.getName()).append(".(").append(ic.getSimpleName()).append(".java:").append(lineNumber).append(')');
+            }
+            sb.append('\n');
+        }
+
+        // print all parameters
+        HttpServletRequest request = controller.getRequest();
+        if (request instanceof JsonRequest) {
+            buildJsonPara(controller, sb);
+        } else {
+            buildPara(controller, sb);
+        }
+
+        sb.append("--------------------------------------------------------------------------------\n");
+
+        try {
+            writer.write(sb.toString());
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+}
+
+

+ 6 - 5
src/main/java/com/jfinal/template/EngineConfig.java

@@ -78,7 +78,7 @@ public class EngineConfig {
 
 	// 浮点数输出与运算时使用的舍入模式,默认值为 "四舍五入"
 	private RoundingMode roundingMode = RoundingMode.HALF_UP;
-	
+
 	private boolean supportStaticMethodExpression = false;
 	private boolean supportStaticFieldExpression = false;
 
@@ -89,6 +89,7 @@ public class EngineConfig {
 
 		// Add official directive of Template Engine
 		addDirective("render", RenderDirective.class, true);
+		addDirective("renderElse", RenderElseDirective.class, true);
 		addDirective("date", DateDirective.class, true);
 		addDirective("escape", EscapeDirective.class, true);
 		addDirective("random", RandomDirective.class, true);
@@ -467,25 +468,25 @@ public class EngineConfig {
 	public RoundingMode getRoundingMode() {
 		return roundingMode;
 	}
-	
+
 	/**
      * 设置为 true 支持静态方法调用表达式,自 jfinal 5.0.2 版本开始默认值为 false
      */
     public void setStaticMethodExpression(boolean enable) {
         this.supportStaticMethodExpression = enable;
     }
-    
+
     public boolean isStaticMethodExpressionEnabled() {
         return supportStaticMethodExpression;
     }
-    
+
     /**
      * 设置为 true 支持静态属性访问表达式,自 jfinal 5.0.2 版本开始默认值为 false
      */
     public void setStaticFieldExpression(boolean enable) {
         this.supportStaticFieldExpression = enable;
     }
-    
+
     public boolean isStaticFieldExpressionEnabled() {
         return supportStaticFieldExpression;
     }

+ 170 - 0
src/main/java/com/jfinal/template/ext/directive/RenderElseDirective.java

@@ -0,0 +1,170 @@
+package com.jfinal.template.ext.directive;
+
+import com.jfinal.kit.PathKit;
+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;
+import com.jfinal.template.stat.Parser;
+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;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * 默认行为与RenderDirective一样
+ * 界面上的自定义render渲染模版
+ * 模版文件存在的时候渲染
+ * 模版文件不存在的时渲染插槽内容
+ * #renderElse("/_view/include/xxx.html")
+ * <a href="/admin/xxx/edit/1">xxxx</a>
+ * #end
+ */
+public class RenderElseDirective extends Directive {
+	private String parentFileName;
+	private Map<String, RenderElseDirective.SubStat> subStatCache = new SyncWriteMap<String, RenderElseDirective.SubStat>(16, 0.5F);
+
+	public void setExprList(ExprList exprList) {
+		int len = exprList.length();
+		if (len == 0) {
+			throw new ParseException("The parameter of #render directive can not be blank", location);
+		}
+		if (len > 1) {
+			for (int i = 1; i < len; i++) {
+				if (!(exprList.getExpr(i) instanceof Assign)) {
+					throw new ParseException("The " + (i + 1) + "th parameter of #render directive must be an assignment expression", location);
+				}
+			}
+		}
+
+		/**
+		 * 从 location 中获取父模板的 fileName,用于生成 subFileName
+		 * 如果是孙子模板,那么 parentFileName 为最顶层的模板,而非直接上层的模板
+		 */
+		this.parentFileName = location.getTemplateFile();
+		this.exprList = exprList;
+	}
+
+	/**
+	 * 对 exprList 进行求值,并将第一个表达式的值作为模板名称返回,
+	 * 开启 local assignment 保障 #render 指令参数表达式列表
+	 * 中的赋值表达式在当前 scope 中进行,有利于模块化
+	 */
+	private Object evalAssignExpressionAndGetFileName(Scope scope) {
+		Ctrl ctrl = scope.getCtrl();
+		try {
+			ctrl.setLocalAssignment();
+			return exprList.evalExprList(scope)[0];
+		} finally {
+			ctrl.setWisdomAssignment();
+		}
+	}
+
+	public void exec(Env env, Scope scope, Writer writer) {
+		// 在 exprList.eval(scope) 之前创建,使赋值表达式在本作用域内进行
+		scope = new Scope(scope);
+
+		Object value = evalAssignExpressionAndGetFileName(scope);
+		if (!(value instanceof String)) {
+			throw new TemplateException("The parameter value of #render directive must be String", location);
+		}
+		String subFileName = Include.getSubFileName((String)value, parentFileName);
+		String enjoyHtmlFilePath = PathKit.getWebRootPath() + subFileName;
+		File enjoyHtmlFile = new File(enjoyHtmlFilePath);
+		if(enjoyHtmlFile.exists()){
+			RenderElseDirective.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);
+				}
+			}
+
+			subStat.exec(null, scope, writer);	// subStat.stat.exec(subStat.env, scope, writer);
+
+			scope.getCtrl().setJumpNone();
+		}else{
+			stat.exec(env, scope, writer);
+		}
+
+	}
+
+	private RenderElseDirective.SubStat parseSubStat(Env env, String subFileName) {
+		EngineConfig config = env.getEngineConfig();
+		// FileSource subFileSource = new FileSource(config.getBaseTemplatePath(), subFileName, config.getEncoding());
+		ISource subFileSource = config.getSourceFactory().getSource(config.getBaseTemplatePath(), subFileName, config.getEncoding());
+
+		try {
+			RenderElseDirective.SubEnv subEnv = new RenderElseDirective.SubEnv(env);
+			StatList subStatList = new Parser(subEnv, subFileSource.getContent(), subFileName).parse();
+			return new RenderElseDirective.SubStat(subEnv, subStatList.getActualStat(), subFileSource);
+		} catch (Exception e) {
+			throw new ParseException(e.getMessage(), location, e);
+		}
+	}
+
+	public static class SubStat extends Stat {
+		public RenderElseDirective.SubEnv env;
+		public Stat stat;
+		public ISource source;
+
+		public SubStat(RenderElseDirective.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);
+		}
+	}
+
+	/**
+	 * SubEnv 用于将子模板与父模板中的模板函数隔离开来,
+	 * 否则在子模板被修改并被重新解析时会再次添加子模板中的
+	 * 模板函数,从而抛出异常
+	 *
+	 * SubEnv 也可以使子模板中定义的模板函数不与上层产生冲突,
+	 * 有利于动态型模板渲染的模块化
+	 *
+	 * 注意: #render 子模板中定义的模板函数无法在父模板中调用
+	 */
+	public static class SubEnv extends Env {
+		public Env parentEnv;
+
+		public SubEnv(Env parentEnv) {
+			super(parentEnv.getEngineConfig());
+			this.parentEnv = parentEnv;
+		}
+
+		/**
+		 * 接管父类 getFunction(),先从子模板中找模板函数,找不到再去父模板中找
+		 */
+		@Override
+		public Define getFunction(String functionName) {
+			Define func = functionMap.get(functionName);
+			return func != null ? func : parentEnv.getFunction(functionName);
+		}
+	}
+
+	@Override
+	public boolean hasEnd() {
+		return true;
+	}
+}