Enjoy 3.2 release ^_^
This commit is contained in:
229
src/main/java/com/jfinal/template/expr/ast/Arith.java
Normal file
229
src/main/java/com/jfinal/template/expr/ast/Arith.java
Normal file
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.expr.Sym;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Arithmetic
|
||||
* 1:支持 byte short int long float double BigDecimal 的 + - * / % 运算
|
||||
* 2:支持字符串加法运算
|
||||
*/
|
||||
public class Arith extends Expr {
|
||||
|
||||
public static final int INT = 0; // byte、short 用 int 类型支持,java 表达式亦如此
|
||||
public static final int LONG = 1;
|
||||
public static final int FLOAT = 2;
|
||||
public static final int DOUBLE = 3;
|
||||
public static final int BIGDECIMAL = 4;
|
||||
|
||||
private Sym op;
|
||||
private Expr left;
|
||||
private Expr right;
|
||||
|
||||
public Arith(Sym op, Expr left, Expr right, Location location) {
|
||||
if (left == null || right == null) {
|
||||
throw new ParseException("The target of \"" + op.value() + "\" operator can not be blank", location);
|
||||
}
|
||||
this.op = op;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
try {
|
||||
return doEval(scope);
|
||||
} catch (TemplateException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
|
||||
private Object doEval(Scope scope) {
|
||||
Object leftValue = left.eval(scope);
|
||||
Object rightValue = right.eval(scope);
|
||||
|
||||
if (leftValue instanceof Number && rightValue instanceof Number) {
|
||||
Number l = (Number)leftValue;
|
||||
Number r = (Number)rightValue;
|
||||
int maxType = getMaxType(l, r);
|
||||
|
||||
switch (op) {
|
||||
case ADD:
|
||||
return add(maxType, l, r);
|
||||
case SUB:
|
||||
return sub(maxType, l, r);
|
||||
case MUL:
|
||||
return mul(maxType, l, r);
|
||||
case DIV:
|
||||
return div(maxType, l, r);
|
||||
case MOD:
|
||||
return mod(maxType, l, r);
|
||||
default :
|
||||
throw new TemplateException("Unsupported operator: " + op.value(), location);
|
||||
}
|
||||
}
|
||||
|
||||
// 字符串加法运算
|
||||
if (leftValue instanceof String || rightValue instanceof String) {
|
||||
return String.valueOf(leftValue).concat(String.valueOf(rightValue));
|
||||
}
|
||||
|
||||
String leftObj = leftValue != null ? leftValue.getClass().getName() : "null";
|
||||
String rightObj = rightValue != null ? rightValue.getClass().getName() : "null";
|
||||
throw new TemplateException("Unsupported operation type: " + leftObj + " " + op.value() + " " + rightObj, location);
|
||||
}
|
||||
|
||||
private int getMaxType(Number obj1, Number obj2) {
|
||||
int t1 = getType(obj1);
|
||||
if (t1 == BIGDECIMAL) {
|
||||
return BIGDECIMAL;
|
||||
}
|
||||
int t2 = getType(obj2);
|
||||
return t1 > t2 ? t1 : t2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注意:调用此方法的前提是,其中有一个对象的类型已经确定是 BigDecimal
|
||||
*/
|
||||
private BigDecimal[] toBigDecimals(Number left, Number right) {
|
||||
BigDecimal[] ret = new BigDecimal[2];
|
||||
if (left instanceof BigDecimal) {
|
||||
ret[0] = (BigDecimal)left;
|
||||
ret[1] = new BigDecimal(right.toString());
|
||||
} else {
|
||||
ret[0] = new BigDecimal(left.toString());
|
||||
ret[1] = (BigDecimal)right;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private int getType(Number obj) {
|
||||
if (obj instanceof Integer) {
|
||||
return INT;
|
||||
} else if (obj instanceof Long) {
|
||||
return LONG;
|
||||
} else if (obj instanceof Float) {
|
||||
return FLOAT;
|
||||
} else if (obj instanceof Double) {
|
||||
return DOUBLE;
|
||||
} else if (obj instanceof BigDecimal) {
|
||||
return BIGDECIMAL;
|
||||
} else if (obj instanceof Short || obj instanceof Byte) {
|
||||
return INT; // short byte 用 int 支持,java 表达式亦如此
|
||||
}
|
||||
throw new TemplateException("Unsupported data type: " + obj.getClass().getName(), location);
|
||||
}
|
||||
|
||||
private Number add(int maxType, Number left, Number right) {
|
||||
switch (maxType) {
|
||||
case INT:
|
||||
return Integer.valueOf(left.intValue() + right.intValue());
|
||||
case LONG:
|
||||
return Long.valueOf(left.longValue() + right.longValue());
|
||||
case FLOAT:
|
||||
return Float.valueOf(left.floatValue() + right.floatValue());
|
||||
case DOUBLE:
|
||||
return Double.valueOf(left.doubleValue() + right.doubleValue());
|
||||
case BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(left, right);
|
||||
return (bd[0]).add(bd[1]);
|
||||
}
|
||||
throw new TemplateException("Unsupported data type", location);
|
||||
}
|
||||
|
||||
private Number sub(int maxType, Number left, Number right) {
|
||||
switch (maxType) {
|
||||
case INT:
|
||||
return Integer.valueOf(left.intValue() - right.intValue());
|
||||
case LONG:
|
||||
return Long.valueOf(left.longValue() - right.longValue());
|
||||
case FLOAT:
|
||||
return Float.valueOf(left.floatValue() - right.floatValue());
|
||||
case DOUBLE:
|
||||
return Double.valueOf(left.doubleValue() - right.doubleValue());
|
||||
case BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(left, right);
|
||||
return (bd[0]).subtract(bd[1]);
|
||||
}
|
||||
throw new TemplateException("Unsupported data type", location);
|
||||
}
|
||||
|
||||
private Number mul(int maxType, Number left, Number right) {
|
||||
switch (maxType) {
|
||||
case INT:
|
||||
return Integer.valueOf(left.intValue() * right.intValue());
|
||||
case LONG:
|
||||
return Long.valueOf(left.longValue() * right.longValue());
|
||||
case FLOAT:
|
||||
return Float.valueOf(left.floatValue() * right.floatValue());
|
||||
case DOUBLE:
|
||||
return Double.valueOf(left.doubleValue() * right.doubleValue());
|
||||
case BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(left, right);
|
||||
return (bd[0]).multiply(bd[1]);
|
||||
}
|
||||
throw new TemplateException("Unsupported data type", location);
|
||||
}
|
||||
|
||||
private Number div(int maxType, Number left, Number right) {
|
||||
switch (maxType) {
|
||||
case INT:
|
||||
return Integer.valueOf(left.intValue() / right.intValue());
|
||||
case LONG:
|
||||
return Long.valueOf(left.longValue() / right.longValue());
|
||||
case FLOAT:
|
||||
return Float.valueOf(left.floatValue() / right.floatValue());
|
||||
case DOUBLE:
|
||||
return Double.valueOf(left.doubleValue() / right.doubleValue());
|
||||
case BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(left, right);
|
||||
// return (bd[0]).divide(bd[1]);
|
||||
return (bd[0]).divide(bd[1], RoundingMode.HALF_EVEN); // 银行家舍入法
|
||||
}
|
||||
throw new TemplateException("Unsupported data type", location);
|
||||
}
|
||||
|
||||
private Number mod(int maxType, Number left, Number right) {
|
||||
switch (maxType) {
|
||||
case INT:
|
||||
return Integer.valueOf(left.intValue() % right.intValue());
|
||||
case LONG:
|
||||
return Long.valueOf(left.longValue() % right.longValue());
|
||||
case FLOAT:
|
||||
return Float.valueOf(left.floatValue() % right.floatValue());
|
||||
case DOUBLE:
|
||||
return Double.valueOf(left.doubleValue() % right.doubleValue());
|
||||
case BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(left, right);
|
||||
return (bd[0]).divideAndRemainder(bd[1])[1];
|
||||
}
|
||||
throw new TemplateException("Unsupported data type", location);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
68
src/main/java/com/jfinal/template/expr/ast/Array.java
Normal file
68
src/main/java/com/jfinal/template/expr/ast/Array.java
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Array
|
||||
*
|
||||
* 用法:
|
||||
* 1:[1, 2, 3]
|
||||
* 2:["a", 1, "b", 2, false, 3.14]
|
||||
*/
|
||||
public class Array extends Expr {
|
||||
|
||||
private Expr[] exprList;
|
||||
|
||||
public Array(Expr[] exprList, Location location) {
|
||||
if (exprList == null) {
|
||||
throw new ParseException("exprList can not be null", location);
|
||||
}
|
||||
this.exprList = exprList;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
List<Object> array = new ArrayListExt(exprList.length);
|
||||
for (Expr expr : exprList) {
|
||||
array.add(expr.eval(scope));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持 array.length 表达式
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public static class ArrayListExt extends ArrayList<Object> {
|
||||
|
||||
public ArrayListExt(int initialCapacity) {
|
||||
super(initialCapacity);
|
||||
}
|
||||
|
||||
public Integer getLength() {
|
||||
return size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
142
src/main/java/com/jfinal/template/expr/ast/Assign.java
Normal file
142
src/main/java/com/jfinal/template/expr/ast/Assign.java
Normal file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Assign
|
||||
*
|
||||
* 支持三种赋值,其中第二种如果括号中是 ID 或 STR 则演变为第三种是对 map 赋值:
|
||||
* 1:ID = expr
|
||||
* 2:ID [ expr ] = expr
|
||||
* 如果 expr 为 int 或 long 型,则是对 array 赋值
|
||||
* 如果 expr 为 ID、STR 型,则是对 map 进行赋值
|
||||
* 否则抛异常出来
|
||||
* 3:ID [ ID ] = expr 或者 ID [ STR ] = expr
|
||||
* 4:支持无限连:id = array[ i = 0 ] = array[1] = 123
|
||||
*/
|
||||
public class Assign extends Expr {
|
||||
|
||||
private String id;
|
||||
private Expr index; // index 用于支持 ID [ expr ] = expr 这种形式
|
||||
private Expr right;
|
||||
|
||||
/**
|
||||
* 数组赋值表达式
|
||||
*/
|
||||
public Assign(String id, Expr index, Expr right, Location location) {
|
||||
if (index == null) {
|
||||
throw new ParseException("The index expression of array assignment can not be null", location);
|
||||
}
|
||||
if (right == null) {
|
||||
throw new ParseException("The expression on the right side of an assignment expression can not be null", location);
|
||||
}
|
||||
this.id = id;
|
||||
this.index = index;
|
||||
this.right = right;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
/**
|
||||
* 普通赋值表达式
|
||||
*/
|
||||
public Assign(String id, Expr right, Location location) {
|
||||
if (right == null) {
|
||||
throw new ParseException("The expression on the right side of an assignment expression can not be null", location);
|
||||
}
|
||||
this.id = id;
|
||||
this.index = null;
|
||||
this.right = right;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
/**
|
||||
* 赋值语句有返回值,可以用于表达式计算
|
||||
*/
|
||||
public Object eval(Scope scope) {
|
||||
if (index == null) {
|
||||
return assignVariable(scope);
|
||||
} else {
|
||||
return assignElement(scope);
|
||||
}
|
||||
}
|
||||
|
||||
Object assignVariable(Scope scope) {
|
||||
Object rightValue = right.eval(scope);
|
||||
if (scope.getCtrl().isWisdomAssignment()) {
|
||||
scope.set(id, rightValue);
|
||||
} else if (scope.getCtrl().isLocalAssignment()) {
|
||||
scope.setLocal(id, rightValue);
|
||||
} else {
|
||||
scope.setGlobal(id, rightValue);
|
||||
}
|
||||
|
||||
return rightValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组或 Map 赋值
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
Object assignElement(Scope scope) {
|
||||
Object target = scope.get(id);
|
||||
if (target == null) {
|
||||
throw new TemplateException("The assigned targets \"" + id + "\" can not be null", location);
|
||||
}
|
||||
Object idx = index.eval(scope);
|
||||
if (idx == null) {
|
||||
throw new TemplateException("The index of list/array and the key of map can not be null", location);
|
||||
}
|
||||
|
||||
Object value;
|
||||
if (target instanceof Map) {
|
||||
value = right.eval(scope);
|
||||
((Map)target).put(idx, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
if ( !(idx instanceof Integer) ) {
|
||||
throw new TemplateException("The index of list/array can only be integer", location);
|
||||
}
|
||||
|
||||
if (target instanceof List) {
|
||||
value = right.eval(scope);
|
||||
((List)target).set((Integer)idx, value);
|
||||
return value;
|
||||
}
|
||||
if (target.getClass().isArray()) {
|
||||
value = right.eval(scope);
|
||||
java.lang.reflect.Array.set(target, (Integer)idx, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new TemplateException("Only the list array and map is supported by index assignment", location);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
288
src/main/java/com/jfinal/template/expr/ast/Compare.java
Normal file
288
src/main/java/com/jfinal/template/expr/ast/Compare.java
Normal file
@@ -0,0 +1,288 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.expr.Sym;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Compare
|
||||
*
|
||||
* 1:支持 byte short int long float double BigDecimal 的 == != > >= < <= 操作
|
||||
* 2:== != 作用于 string,调用其 equals 方法进行比较
|
||||
* 3:> >= < <= 可以比较实现了 Comparable 接口的对象
|
||||
*
|
||||
* 注意:float double 浮点型数据在比较操作时,具有精度上的局限性,不建议对浮点数进行比较
|
||||
*/
|
||||
public class Compare extends Expr {
|
||||
|
||||
private Sym op;
|
||||
private Expr left;
|
||||
private Expr right;
|
||||
|
||||
public Compare(Sym op, Expr left, Expr right, Location location) {
|
||||
if (left == null || right == null) {
|
||||
throw new ParseException("The target of \"" + op.value() + "\" operator can not be blank", location);
|
||||
}
|
||||
this.op = op;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
Object leftValue = left.eval(scope);
|
||||
Object rightValue = right.eval(scope);
|
||||
|
||||
switch(op) {
|
||||
case EQUAL:
|
||||
return equal(leftValue, rightValue);
|
||||
case NOTEQUAL:
|
||||
return ! equal(leftValue, rightValue);
|
||||
case GT:
|
||||
return gt(leftValue, rightValue);
|
||||
case GE:
|
||||
return ge(leftValue, rightValue);
|
||||
case LT:
|
||||
return lt(leftValue, rightValue);
|
||||
case LE:
|
||||
return le(leftValue, rightValue);
|
||||
default:
|
||||
String l = leftValue != null ? leftValue.getClass().getSimpleName() : "null";
|
||||
String r = rightValue != null ? rightValue.getClass().getSimpleName() : "null";
|
||||
throw new TemplateException("Unsupported operation: " + l + " \"" + op.value() + "\" " + r, location);
|
||||
}
|
||||
}
|
||||
|
||||
Boolean equal(Object leftValue, Object rightValue) {
|
||||
if (leftValue == rightValue) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
if (leftValue == null || rightValue == null) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
if (leftValue.equals(rightValue)) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
if (leftValue instanceof Number && rightValue instanceof Number) {
|
||||
Number l = (Number)leftValue;
|
||||
Number r = (Number)rightValue;
|
||||
int maxType = getMaxType(l, r);
|
||||
switch (maxType) {
|
||||
case Arith.INT:
|
||||
return l.intValue() == r.intValue();
|
||||
case Arith.LONG:
|
||||
return l.longValue() == r.longValue();
|
||||
case Arith.FLOAT:
|
||||
// 此法仅适用于两个对象类型相同的情况,升级为 BigDecimal 后精度会再高几个数量级
|
||||
// return Float.floatToIntBits(l.floatValue()) == Float.floatToIntBits(r.floatValue());
|
||||
case Arith.DOUBLE:
|
||||
// 此法仅适用于两个对象类型相同的情况,升级为 BigDecimal 后精度会再高几个数量级
|
||||
// return Double.doubleToLongBits(l.doubleValue()) == Double.doubleToLongBits(r.doubleValue());
|
||||
case Arith.BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(l, r);
|
||||
return (bd[0]).compareTo(bd[1]) == 0;
|
||||
}
|
||||
throw new TemplateException("Equal comparison support types of int long float double and BigDeciaml", location);
|
||||
}
|
||||
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
Boolean gt(Object leftValue, Object rightValue) {
|
||||
if (leftValue instanceof Number && rightValue instanceof Number) {
|
||||
Number l = (Number)leftValue;
|
||||
Number r = (Number)rightValue;
|
||||
int maxType = getMaxType(l, r);
|
||||
switch (maxType) {
|
||||
case Arith.INT:
|
||||
return l.intValue() > r.intValue();
|
||||
case Arith.LONG:
|
||||
return l.longValue() > r.longValue();
|
||||
case Arith.FLOAT:
|
||||
// return Float.floatToIntBits(l.floatValue()) > Float.floatToIntBits(r.floatValue());
|
||||
case Arith.DOUBLE:
|
||||
// return Double.doubleToLongBits(l.doubleValue()) > Double.doubleToLongBits(r.doubleValue());
|
||||
case Arith.BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(l, r);
|
||||
return (bd[0]).compareTo(bd[1]) > 0;
|
||||
}
|
||||
throw new TemplateException("Unsupported operation: " + l.getClass().getSimpleName() + " \">\" " + r.getClass().getSimpleName(), location);
|
||||
}
|
||||
|
||||
if (leftValue instanceof Comparable &&
|
||||
leftValue.getClass() == rightValue.getClass()) {
|
||||
return ((Comparable)leftValue).compareTo((Comparable)rightValue) > 0;
|
||||
}
|
||||
|
||||
return checkType(leftValue, rightValue);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
Boolean ge(Object leftValue, Object rightValue) {
|
||||
if (leftValue instanceof Number && rightValue instanceof Number) {
|
||||
Number l = (Number)leftValue;
|
||||
Number r = (Number)rightValue;
|
||||
int maxType = getMaxType(l, r);
|
||||
switch (maxType) {
|
||||
case Arith.INT:
|
||||
return l.intValue() >= r.intValue();
|
||||
case Arith.LONG:
|
||||
return l.longValue() >= r.longValue();
|
||||
case Arith.FLOAT:
|
||||
// return Float.floatToIntBits(l.floatValue()) >= Float.floatToIntBits(r.floatValue());
|
||||
case Arith.DOUBLE:
|
||||
// return Double.doubleToLongBits(l.doubleValue()) >= Double.doubleToLongBits(r.doubleValue());
|
||||
case Arith.BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(l, r);
|
||||
return (bd[0]).compareTo(bd[1]) >= 0;
|
||||
}
|
||||
throw new TemplateException("Unsupported operation: " + l.getClass().getSimpleName() + " \">=\" " + r.getClass().getSimpleName(), location);
|
||||
}
|
||||
|
||||
if (leftValue instanceof Comparable &&
|
||||
leftValue.getClass() == rightValue.getClass()) {
|
||||
return ((Comparable)leftValue).compareTo((Comparable)rightValue) >= 0;
|
||||
}
|
||||
|
||||
return checkType(leftValue, rightValue);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
Boolean lt(Object leftValue, Object rightValue) {
|
||||
if (leftValue instanceof Number && rightValue instanceof Number) {
|
||||
Number l = (Number)leftValue;
|
||||
Number r = (Number)rightValue;
|
||||
int maxType = getMaxType(l, r);
|
||||
switch (maxType) {
|
||||
case Arith.INT:
|
||||
return l.intValue() < r.intValue();
|
||||
case Arith.LONG:
|
||||
return l.longValue() < r.longValue();
|
||||
case Arith.FLOAT:
|
||||
// return Float.floatToIntBits(l.floatValue()) < Float.floatToIntBits(r.floatValue());
|
||||
case Arith.DOUBLE:
|
||||
// return Double.doubleToLongBits(l.doubleValue()) < Double.doubleToLongBits(r.doubleValue());
|
||||
case Arith.BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(l, r);
|
||||
return (bd[0]).compareTo(bd[1]) < 0;
|
||||
}
|
||||
throw new TemplateException("Unsupported operation: " + l.getClass().getSimpleName() + " \"<\" " + r.getClass().getSimpleName(), location);
|
||||
}
|
||||
|
||||
if (leftValue instanceof Comparable &&
|
||||
leftValue.getClass() == rightValue.getClass()) {
|
||||
return ((Comparable)leftValue).compareTo((Comparable)rightValue) < 0;
|
||||
}
|
||||
|
||||
return checkType(leftValue, rightValue);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
Boolean le(Object leftValue, Object rightValue) {
|
||||
if (leftValue instanceof Number && rightValue instanceof Number) {
|
||||
Number l = (Number)leftValue;
|
||||
Number r = (Number)rightValue;
|
||||
int maxType = getMaxType(l, r);
|
||||
switch (maxType) {
|
||||
case Arith.INT:
|
||||
return l.intValue() <= r.intValue();
|
||||
case Arith.LONG:
|
||||
return l.longValue() <= r.longValue();
|
||||
case Arith.FLOAT:
|
||||
// return Float.floatToIntBits(l.floatValue()) <= Float.floatToIntBits(r.floatValue());
|
||||
case Arith.DOUBLE:
|
||||
// return Double.doubleToLongBits(l.doubleValue()) <= Double.doubleToLongBits(r.doubleValue());
|
||||
case Arith.BIGDECIMAL:
|
||||
BigDecimal[] bd = toBigDecimals(l, r);
|
||||
return (bd[0]).compareTo(bd[1]) <= 0;
|
||||
}
|
||||
throw new TemplateException("Unsupported operation: " + l.getClass().getSimpleName() + " \"<=\" " + r.getClass().getSimpleName(), location);
|
||||
}
|
||||
|
||||
if (leftValue instanceof Comparable &&
|
||||
leftValue.getClass() == rightValue.getClass()) {
|
||||
return ((Comparable)leftValue).compareTo((Comparable)rightValue) <= 0;
|
||||
}
|
||||
|
||||
return checkType(leftValue, rightValue);
|
||||
}
|
||||
|
||||
private int getMaxType(Number obj1, Number obj2) {
|
||||
int t1 = getType(obj1);
|
||||
if (t1 == Arith.BIGDECIMAL) {
|
||||
return Arith.BIGDECIMAL;
|
||||
}
|
||||
int t2 = getType(obj2);
|
||||
return t1 > t2 ? t1 : t2;
|
||||
}
|
||||
|
||||
private int getType(Number obj) {
|
||||
if (obj instanceof Integer) {
|
||||
return Arith.INT;
|
||||
} else if (obj instanceof Long) {
|
||||
return Arith.LONG;
|
||||
} else if (obj instanceof Float) {
|
||||
return Arith.FLOAT;
|
||||
} else if (obj instanceof Double) {
|
||||
return Arith.DOUBLE;
|
||||
} else if (obj instanceof BigDecimal) {
|
||||
return Arith.BIGDECIMAL;
|
||||
} else if (obj instanceof Short || obj instanceof Byte) {
|
||||
return Arith.INT; // short byte 用 int 支持,java 表达式亦如此
|
||||
}
|
||||
throw new TemplateException("Unsupported data type: " + obj.getClass().getName(), location);
|
||||
}
|
||||
|
||||
BigDecimal[] toBigDecimals(Number left, Number right) {
|
||||
BigDecimal[] ret = new BigDecimal[2];
|
||||
ret[0] = (left instanceof BigDecimal ? (BigDecimal)left : new BigDecimal(left.toString()));
|
||||
ret[1] = (right instanceof BigDecimal ? (BigDecimal)right : new BigDecimal(right.toString()));
|
||||
return ret;
|
||||
}
|
||||
|
||||
private Boolean checkType(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);
|
||||
}
|
||||
if (rightValue == null) {
|
||||
throw new TemplateException("The operation target on the right side of \"" + op.value() + "\" can not be null", location);
|
||||
}
|
||||
|
||||
throw new TemplateException(
|
||||
"Unsupported operation: " +
|
||||
leftValue.getClass().getSimpleName() +
|
||||
" \"" + op.value() + "\" " +
|
||||
rightValue.getClass().getSimpleName(),
|
||||
location
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
150
src/main/java/com/jfinal/template/expr/ast/Const.java
Normal file
150
src/main/java/com/jfinal/template/expr/ast/Const.java
Normal file
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import com.jfinal.template.expr.Sym;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
private Sym type;
|
||||
private Object value;
|
||||
|
||||
/**
|
||||
* 构造 TRUE FALSE NULL 常量,无需对 value 进行转换
|
||||
*/
|
||||
private Const(Object value, Sym type) {
|
||||
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;
|
||||
}
|
||||
|
||||
public boolean isTrue() {
|
||||
return type == Sym.TRUE;
|
||||
}
|
||||
|
||||
public boolean isFalse() {
|
||||
return type == Sym.FALSE;
|
||||
}
|
||||
|
||||
public boolean isBoolean() {
|
||||
return type == Sym.TRUE || type == Sym.FALSE;
|
||||
}
|
||||
|
||||
public boolean isNull() {
|
||||
return type == Sym.NULL;
|
||||
}
|
||||
|
||||
public boolean isInt() {
|
||||
return type == Sym.INT;
|
||||
}
|
||||
|
||||
public boolean isLong() {
|
||||
return type == Sym.LONG;
|
||||
}
|
||||
|
||||
public boolean isFloat() {
|
||||
return type == Sym.FLOAT;
|
||||
}
|
||||
|
||||
public boolean isDouble() {
|
||||
return type == Sym.DOUBLE;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getStr() {
|
||||
return (String)value;
|
||||
}
|
||||
|
||||
public Boolean getBoolean() {
|
||||
return (Boolean)value;
|
||||
}
|
||||
|
||||
public Integer getInt() {
|
||||
return (Integer)value;
|
||||
}
|
||||
|
||||
public Long getLong() {
|
||||
return (Long)value;
|
||||
}
|
||||
|
||||
public Float getFloat() {
|
||||
return (Float)value;
|
||||
}
|
||||
|
||||
public Double getDouble() {
|
||||
return (Double)value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
34
src/main/java/com/jfinal/template/expr/ast/Expr.java
Normal file
34
src/main/java/com/jfinal/template/expr/ast/Expr.java
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Expr
|
||||
*/
|
||||
public abstract class Expr {
|
||||
|
||||
protected Location location;
|
||||
|
||||
public abstract Object eval(Scope scope);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
89
src/main/java/com/jfinal/template/expr/ast/ExprList.java
Normal file
89
src/main/java/com/jfinal/template/expr/ast/ExprList.java
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import java.util.List;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* ExprList
|
||||
*/
|
||||
public class ExprList extends Expr {
|
||||
|
||||
public static final Expr[] NULL_EXPR_ARRAY = new 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) {
|
||||
exprArray = exprList.toArray(new Expr[exprList.size()]);
|
||||
} else {
|
||||
exprArray = NULL_EXPR_ARRAY;
|
||||
}
|
||||
}
|
||||
|
||||
public Expr[] getExprArray() {
|
||||
return exprArray;
|
||||
}
|
||||
|
||||
public Expr getExpr(int index) {
|
||||
if (index < 0 || index >= exprArray.length) {
|
||||
throw new TemplateException("Index out of bounds: index = " + index + ", length = " + exprArray.length, location);
|
||||
}
|
||||
return exprArray[index];
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return exprArray.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对所有表达式求值,只返回最后一个表达式的值
|
||||
*/
|
||||
public Object eval(Scope scope) {
|
||||
Object ret = null;
|
||||
for (Expr expr : exprArray) {
|
||||
ret = expr.eval(scope);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对所有表达式求值,并返回所有表达式的值
|
||||
*/
|
||||
public Object[] evalExprList(Scope scope) {
|
||||
if (exprArray.length == 0) {
|
||||
return NULL_OBJECT_ARRAY;
|
||||
}
|
||||
|
||||
Object[] ret = new Object[exprArray.length];
|
||||
for (int i=0; i<exprArray.length; i++) {
|
||||
ret[i] = exprArray[i].eval(scope);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
121
src/main/java/com/jfinal/template/expr/ast/Field.java
Normal file
121
src/main/java/com/jfinal/template/expr/ast/Field.java
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
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;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Field
|
||||
*
|
||||
* 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 属性值
|
||||
*/
|
||||
public class Field extends Expr {
|
||||
|
||||
private Expr expr;
|
||||
private String fieldName;
|
||||
private String getterName;
|
||||
|
||||
public Field(Expr expr, String fieldName, Location location) {
|
||||
if (expr == null) {
|
||||
throw new ParseException("The object for field access can not be null", location);
|
||||
}
|
||||
this.expr = expr;
|
||||
this.fieldName = fieldName;
|
||||
this.getterName = "get" + StrKit.firstCharToUpperCase(fieldName);
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
Object target = expr.eval(scope);
|
||||
if (target == null) {
|
||||
if (scope.getCtrl().isNullSafe()) {
|
||||
return null;
|
||||
}
|
||||
if (expr instanceof Id) {
|
||||
String id = ((Id)expr).getId();
|
||||
throw new TemplateException("\"" + id + "\" can not be null for accessed by \"" + id + "." + fieldName + "\"", location);
|
||||
}
|
||||
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);
|
||||
} 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("Field not found: \"" + fieldName + "\" and getter method not found: \"" + getterName + "()\"", location);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
65
src/main/java/com/jfinal/template/expr/ast/FieldKit.java
Normal file
65
src/main/java/com/jfinal/template/expr/ast/FieldKit.java
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* FieldKit
|
||||
*/
|
||||
public class FieldKit {
|
||||
|
||||
private static final ConcurrentHashMap<String, Object> fieldCache = new ConcurrentHashMap<String, Object>();
|
||||
|
||||
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 Field doGetField(Class<?> targetClass, String fieldName) {
|
||||
Field[] fs = targetClass.getFields();
|
||||
for (Field f : fs) {
|
||||
if (f.getName().equals(fieldName)) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Field 用于缓存的 key
|
||||
*/
|
||||
public static String getFieldKey(Class<?> targetClass, String getterName) {
|
||||
return new StringBuilder(64).append(targetClass.getName())
|
||||
.append('.').append(getterName).toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
99
src/main/java/com/jfinal/template/expr/ast/ForCtrl.java
Normal file
99
src/main/java/com/jfinal/template/expr/ast/ForCtrl.java
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* forCtrl : ID : expression
|
||||
* | exprList? ';' expr? ';' exprList?
|
||||
*
|
||||
* 两种用法
|
||||
* 1:#for(id : list) #end
|
||||
* #for(entry : map) #end
|
||||
*
|
||||
* 2:#for(init; cond; update) #end
|
||||
*/
|
||||
public class ForCtrl extends Expr {
|
||||
|
||||
private String id;
|
||||
private Expr expr;
|
||||
|
||||
private Expr init;
|
||||
private Expr cond;
|
||||
private Expr update;
|
||||
|
||||
/**
|
||||
* ID : expr
|
||||
*/
|
||||
public ForCtrl(Id id, Expr expr, Location location) {
|
||||
if (expr == null) {
|
||||
throw new ParseException("The iterator target of #for statement can not be null", location);
|
||||
}
|
||||
this.id = id.getId();
|
||||
this.expr = expr;
|
||||
this.init = null;
|
||||
this.cond = null;
|
||||
this.update = null;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
/**
|
||||
* exprList? ';' expr? ';' exprList?
|
||||
*/
|
||||
public ForCtrl(Expr init, Expr cond, Expr update, Location location) {
|
||||
this.init = init;
|
||||
this.cond = cond;
|
||||
this.update = update;
|
||||
this.id = null;
|
||||
this.expr = null;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public boolean isIterator() {
|
||||
return id != null;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Expr getExpr() {
|
||||
return expr;
|
||||
}
|
||||
|
||||
public Expr getInit() {
|
||||
return init;
|
||||
}
|
||||
|
||||
public Expr getCond() {
|
||||
return cond;
|
||||
}
|
||||
|
||||
public Expr getUpdate() {
|
||||
return update;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
throw new TemplateException("The eval(Scope scope) method can not be invoked", location);
|
||||
}
|
||||
}
|
||||
|
||||
|
48
src/main/java/com/jfinal/template/expr/ast/Id.java
Normal file
48
src/main/java/com/jfinal/template/expr/ast/Id.java
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Id
|
||||
*/
|
||||
public class Id extends Expr {
|
||||
|
||||
private String id;
|
||||
|
||||
public Id(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
return scope.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Id.toString() 后续版本不能变动,已有部分第三方依赖此方法
|
||||
*/
|
||||
public String toString() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
|
138
src/main/java/com/jfinal/template/expr/ast/IncDec.java
Normal file
138
src/main/java/com/jfinal/template/expr/ast/IncDec.java
Normal file
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Map;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.expr.Sym;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* 自增与自减
|
||||
*/
|
||||
public class IncDec extends Expr {
|
||||
|
||||
private Sym op;
|
||||
private String id;
|
||||
private boolean isPost; // 是否是后缀形式: i++ i--
|
||||
|
||||
public IncDec(Sym op, boolean isPost, Expr id, Location location) {
|
||||
if (id == null) {
|
||||
throw new ParseException(op.value() + " operator requires target to be operational", location);
|
||||
}
|
||||
if ( !(id instanceof Id) ) {
|
||||
throw new ParseException(op.value() + " operator only supports identifiers", location);
|
||||
}
|
||||
|
||||
this.op = op;
|
||||
this.id = ((Id)id).getId();
|
||||
this.isPost = isPost;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public Object eval(Scope scope) {
|
||||
Map map = scope.getMapOfValue(id);
|
||||
if (map == null) {
|
||||
if (scope.getCtrl().isNullSafe()) {
|
||||
return null;
|
||||
}
|
||||
throw new TemplateException("The target of " + op.value() + " operator can not be null", location);
|
||||
}
|
||||
Object value = map.get(id);
|
||||
if ( !(value instanceof Number) ) {
|
||||
throw new TemplateException(op.value() + " operator only support int long float double and BigDecimal type", location);
|
||||
}
|
||||
|
||||
Number newValue;
|
||||
switch (op) {
|
||||
case INC:
|
||||
newValue = inc((Number)value);
|
||||
break ;
|
||||
case DEC:
|
||||
newValue = dec((Number)value);
|
||||
break ;
|
||||
default:
|
||||
throw new TemplateException("Unsupported operator: " + op.value(), location);
|
||||
}
|
||||
map.put(id, newValue);
|
||||
return isPost ? value : newValue;
|
||||
}
|
||||
|
||||
private Number inc(Number num) {
|
||||
if (num instanceof Integer) {
|
||||
return Integer.valueOf(num.intValue() + 1);
|
||||
}
|
||||
if (num instanceof Long) {
|
||||
return Long.valueOf(num.longValue() + 1L);
|
||||
}
|
||||
if (num instanceof Float) {
|
||||
return Float.valueOf(num.floatValue() + 1F);
|
||||
}
|
||||
if (num instanceof Double) {
|
||||
return Double.valueOf(num.doubleValue() + 1D);
|
||||
}
|
||||
if (num instanceof BigDecimal) {
|
||||
return ((BigDecimal)num).add(BigDecimal.ONE);
|
||||
}
|
||||
if (num instanceof BigInteger) {
|
||||
return ((BigInteger)num).add(BigInteger.ONE);
|
||||
}
|
||||
if (num instanceof Short) {
|
||||
return (short)(((Short)num).shortValue() + 1);
|
||||
}
|
||||
if (num instanceof Byte) {
|
||||
return (byte)(((Byte)num).byteValue() + 1);
|
||||
}
|
||||
return num.intValue() + 1;
|
||||
}
|
||||
|
||||
private Number dec(Number num) {
|
||||
if (num instanceof Integer) {
|
||||
return Integer.valueOf(num.intValue() - 1);
|
||||
}
|
||||
if (num instanceof Long) {
|
||||
return Long.valueOf(num.longValue() - 1L);
|
||||
}
|
||||
if (num instanceof Float) {
|
||||
return Float.valueOf(num.floatValue() - 1F);
|
||||
}
|
||||
if (num instanceof Double) {
|
||||
return Double.valueOf(num.doubleValue() - 1D);
|
||||
}
|
||||
if (num instanceof BigDecimal) {
|
||||
return ((BigDecimal)num).subtract(BigDecimal.ONE);
|
||||
}
|
||||
if (num instanceof BigInteger) {
|
||||
return ((BigInteger)num).subtract(BigInteger.ONE);
|
||||
}
|
||||
if (num instanceof Short) {
|
||||
return (short)(((Short)num).shortValue() - 1);
|
||||
}
|
||||
if (num instanceof Byte) {
|
||||
return (byte)(((Byte)num).byteValue() - 1);
|
||||
}
|
||||
return num.intValue() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
86
src/main/java/com/jfinal/template/expr/ast/Index.java
Normal file
86
src/main/java/com/jfinal/template/expr/ast/Index.java
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import java.util.List;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* index : expr '[' expr ']'
|
||||
*
|
||||
* 支持 a[i]、 a[b[i]]、a[i][j]、a[i][j]...[n]
|
||||
*/
|
||||
public class Index extends Expr {
|
||||
|
||||
private Expr expr;
|
||||
private Expr index;
|
||||
|
||||
public Index(Expr expr, Expr index, Location location) {
|
||||
if (expr == null || index == null) {
|
||||
throw new ParseException("array/list/map and their index can not be null", location);
|
||||
}
|
||||
this.expr = expr;
|
||||
this.index = index;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Object eval(Scope scope) {
|
||||
Object array = expr.eval(scope);
|
||||
if (array == null) {
|
||||
if (scope.getCtrl().isNullSafe()) {
|
||||
return null;
|
||||
}
|
||||
throw new TemplateException("The index access operation target can not be null", location);
|
||||
}
|
||||
|
||||
Object idx = index.eval(scope);
|
||||
if (idx == null) {
|
||||
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);
|
||||
}
|
||||
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 (idx instanceof Integer) {
|
||||
return java.lang.reflect.Array.get(array, (Integer)idx);
|
||||
}
|
||||
throw new TemplateException("The index of array can only be integer", location);
|
||||
}
|
||||
|
||||
throw new TemplateException("Only the list array and map is supported by index access", location);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
145
src/main/java/com/jfinal/template/expr/ast/Logic.java
Normal file
145
src/main/java/com/jfinal/template/expr/ast/Logic.java
Normal file
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* 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.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;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Logic
|
||||
*
|
||||
* 支持逻辑运算: ! && ||
|
||||
*/
|
||||
public class Logic extends Expr {
|
||||
|
||||
private Sym op;
|
||||
private Expr left; // ! 运算没有 left 参数
|
||||
private Expr right;
|
||||
|
||||
/**
|
||||
* 构造 || && 结点
|
||||
*/
|
||||
public Logic(Sym op, Expr left, Expr right, Location location) {
|
||||
if (left == null) {
|
||||
throw new ParseException("The target of \"" + op.value() + "\" operator on the left side can not be blank", location);
|
||||
}
|
||||
if (right == null) {
|
||||
throw new ParseException("The target of \"" + op.value() + "\" operator on the right side can not be blank", location);
|
||||
}
|
||||
this.op = op;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造 ! 结点,left 为 null
|
||||
*/
|
||||
public Logic(Sym op, Expr right, Location location) {
|
||||
if (right == null) {
|
||||
throw new ParseException("The target of \"" + op.value() + "\" operator on the right side can not be blank", location);
|
||||
}
|
||||
this.op = op;
|
||||
this.left = null;
|
||||
this.right = right;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
switch (op) {
|
||||
case NOT:
|
||||
return evalNot(scope);
|
||||
case AND:
|
||||
return evalAnd(scope);
|
||||
case OR:
|
||||
return evalOr(scope);
|
||||
default:
|
||||
throw new TemplateException("Unsupported operator: " + op.value(), location);
|
||||
}
|
||||
}
|
||||
|
||||
Object evalNot(Scope scope) {
|
||||
return ! isTrue(right.eval(scope));
|
||||
}
|
||||
|
||||
Object evalAnd(Scope scope) {
|
||||
return isTrue(left.eval(scope)) && isTrue(right.eval(scope));
|
||||
}
|
||||
|
||||
Object evalOr(Scope scope) {
|
||||
return isTrue(left.eval(scope)) || isTrue(right.eval(scope));
|
||||
}
|
||||
|
||||
/**
|
||||
* 规则:
|
||||
* 1:null 返回 false
|
||||
* 2:boolean 类型,原值返回
|
||||
* 3:Map、Connection(List被包括在内) 返回 size() > 0
|
||||
* 4:数组,返回 length > 0
|
||||
* 5:String、StringBuilder、StringBuffer 等继承自 CharSequence 类的对象,返回 length > 0
|
||||
* 6:Number 类型,返回 value != 0
|
||||
* 7:Iterator 返回 hasNext() 值
|
||||
* 8:其它返回 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;
|
||||
}
|
||||
|
||||
public static boolean isFalse(Object v) {
|
||||
return !isTrue(v);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
67
src/main/java/com/jfinal/template/expr/ast/Map.java
Normal file
67
src/main/java/com/jfinal/template/expr/ast/Map.java
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map.Entry;
|
||||
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 可以是任意的常量与表达式
|
||||
*
|
||||
* 2:取值
|
||||
* 先将 Map 常量赋值给某个变量: #set(map = {...})
|
||||
* map['k1']
|
||||
* map["k1"]
|
||||
* map[expr]
|
||||
* map.get("k1")
|
||||
* map.k1
|
||||
*
|
||||
* 如上所示,当以下标方式取值时,下标参数可以是 string 与 expr,而 expr 求值以后的值必须也为 string类型
|
||||
* 当用 map.k1 这类 field 字段取值形式时,则是使用 id 标识符,而不是 string 形参数
|
||||
*
|
||||
* 注意:即便是定义的时候 key 用的是 id 标识符,但在取值时也要用 string 类型下标参数或 expr 求值后为 string
|
||||
* 定义时 key 可以使用 id 标识符是为了书写方便,本质上仍然是 string
|
||||
*
|
||||
* 3:可创建空 map,如: #(map = {})
|
||||
*/
|
||||
public class Map extends Expr {
|
||||
|
||||
private LinkedHashMap<Object, Expr> map;
|
||||
|
||||
public Map(LinkedHashMap<Object, Expr> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
LinkedHashMap<Object, Object> valueMap = new LinkedHashMap<Object, Object>(map.size());
|
||||
for (Entry<Object, Expr> e : map.entrySet()) {
|
||||
valueMap.put(e.getKey(), e.getValue().eval(scope));
|
||||
}
|
||||
return valueMap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
129
src/main/java/com/jfinal/template/expr/ast/Method.java
Normal file
129
src/main/java/com/jfinal/template/expr/ast/Method.java
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Method : expr '.' ID '(' exprList? ')'
|
||||
*/
|
||||
public class Method extends Expr {
|
||||
|
||||
private Expr expr;
|
||||
private String methodName;
|
||||
private ExprList exprList;
|
||||
|
||||
public Method(Expr expr, String methodName, ExprList exprList, Location location) {
|
||||
if (exprList == null || exprList.length() == 0) {
|
||||
throw new ParseException("The parameter of method can not be blank", location);
|
||||
}
|
||||
init(expr, methodName, exprList, location);
|
||||
}
|
||||
|
||||
public Method(Expr expr, String methodName, Location location) {
|
||||
init(expr, methodName, ExprList.NULL_EXPR_LIST, location);
|
||||
}
|
||||
|
||||
private void init(Expr expr, String methodName, ExprList exprList, Location location) {
|
||||
if (expr == null) {
|
||||
throw new ParseException("The target for method invoking can not be blank", location);
|
||||
}
|
||||
if (MethodKit.isForbiddenMethod(methodName)) {
|
||||
throw new ParseException("Forbidden method: " + methodName, location);
|
||||
}
|
||||
this.expr = expr;
|
||||
this.methodName = methodName;
|
||||
this.exprList = exprList;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
Object target = expr.eval(scope);
|
||||
if (target == null) {
|
||||
if (scope.getCtrl().isNullSafe()) {
|
||||
return null;
|
||||
}
|
||||
throw new TemplateException("The target for method invoking can not be null, method name: " + methodName, location);
|
||||
}
|
||||
|
||||
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) {
|
||||
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);
|
||||
} 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);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
|
||||
static String buildMethodNotFoundSignature(String preMsg, String methodName, Object[] argValues) {
|
||||
StringBuilder ret = new StringBuilder().append(preMsg).append(methodName).append("(");
|
||||
if (argValues != null) {
|
||||
for (int i = 0; i < argValues.length; i++) {
|
||||
if (i > 0) {
|
||||
ret.append(", ");
|
||||
}
|
||||
ret.append(argValues[i] != null ? argValues[i].getClass().getName() : "null");
|
||||
}
|
||||
}
|
||||
return ret.append(")").toString();
|
||||
}
|
||||
|
||||
/*
|
||||
public static Object invokeVarArgsMethod(java.lang.reflect.Method method, Object target, Object[] argValues) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
Class<?>[] paraTypes = method.getParameterTypes();
|
||||
Object[] finalArgValues = new Object[paraTypes.length];
|
||||
|
||||
int fixedParaLength = paraTypes.length - 1;
|
||||
System.arraycopy(argValues, 0, finalArgValues, 0, fixedParaLength);
|
||||
Class<?> varParaComponentType = paraTypes[paraTypes.length - 1].getComponentType();
|
||||
Object varParaValues = Array.newInstance(varParaComponentType, argValues.length - fixedParaLength);
|
||||
int p = 0;
|
||||
for (int i=fixedParaLength; i<argValues.length; i++) {
|
||||
Array.set(varParaValues, p++, argValues[i]);
|
||||
}
|
||||
finalArgValues[paraTypes.length - 1] = varParaValues;
|
||||
return method.invoke(target, finalArgValues);
|
||||
}*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
99
src/main/java/com/jfinal/template/expr/ast/MethodInfo.java
Normal file
99
src/main/java/com/jfinal/template/expr/ast/MethodInfo.java
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* MethodInfo
|
||||
*/
|
||||
public class MethodInfo {
|
||||
|
||||
protected final String 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) {
|
||||
this.key = key;
|
||||
this.clazz = clazz;
|
||||
this.method = method;
|
||||
this.isVarArgs = method.isVarArgs();
|
||||
this.paraTypes = method.getParameterTypes();
|
||||
}
|
||||
|
||||
public Object invoke(Object target, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
if (isVarArgs) {
|
||||
return invokeVarArgsMethod(target, args);
|
||||
} else {
|
||||
return method.invoke(target, args);
|
||||
}
|
||||
}
|
||||
|
||||
protected Object invokeVarArgsMethod(Object target, Object[] argValues) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
Object[] finalArgValues = new Object[paraTypes.length];
|
||||
|
||||
int fixedParaLength = paraTypes.length - 1;
|
||||
System.arraycopy(argValues, 0, finalArgValues, 0, fixedParaLength);
|
||||
Class<?> varParaComponentType = paraTypes[paraTypes.length - 1].getComponentType();
|
||||
Object varParaValues = Array.newInstance(varParaComponentType, argValues.length - fixedParaLength);
|
||||
int p = 0;
|
||||
for (int i=fixedParaLength; i<argValues.length; i++) {
|
||||
Array.set(varParaValues, p++, argValues[i]);
|
||||
}
|
||||
finalArgValues[paraTypes.length - 1] = varParaValues;
|
||||
return method.invoke(target, finalArgValues);
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return method.getName();
|
||||
}
|
||||
|
||||
public boolean isStatic() {
|
||||
return Modifier.isStatic(method.getModifiers());
|
||||
}
|
||||
|
||||
public boolean isVarArgs() {
|
||||
return isVarArgs;
|
||||
}
|
||||
|
||||
protected Class<?>[] getParameterTypes() {
|
||||
return paraTypes;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder ret = new StringBuilder(clazz.getName()).append(".").append(method.getName()).append("(");
|
||||
for (int i=0; i<paraTypes.length; i++) {
|
||||
if (i > 0) {
|
||||
ret.append(", ");
|
||||
}
|
||||
ret.append(paraTypes[i].getName());
|
||||
}
|
||||
return ret.append(")").toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* MethodInfoExt 辅助实现 extension method 功能
|
||||
*/
|
||||
public class MethodInfoExt extends MethodInfo {
|
||||
|
||||
protected Object objectOfExtensionClass;
|
||||
|
||||
public MethodInfoExt(Object objectOfExtensionClass, String key, Class<?> clazz, Method method) {
|
||||
super(key, clazz, method);
|
||||
this.objectOfExtensionClass = objectOfExtensionClass;
|
||||
|
||||
// 将被 mixed 的类自身添加入参数类型数组的第一个位置
|
||||
// Class<?>[] newParaTypes = new Class<?>[paraTypes.length + 1];
|
||||
// newParaTypes[0] = clazz; // 第一个参数就是被 mixed 的类它自己
|
||||
// System.arraycopy(paraTypes, 0, newParaTypes, 1, paraTypes.length);
|
||||
// this.paraTypes = newParaTypes;
|
||||
}
|
||||
|
||||
public Object invoke(Object target, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
Object[] finalArgs = new Object[args.length + 1];
|
||||
finalArgs[0] = target;
|
||||
|
||||
if (args.length > 0) {
|
||||
System.arraycopy(args, 0, finalArgs, 1, args.length);
|
||||
}
|
||||
|
||||
if (isVarArgs) {
|
||||
return invokeVarArgsMethod(objectOfExtensionClass, finalArgs);
|
||||
} else {
|
||||
return method.invoke(objectOfExtensionClass, finalArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
393
src/main/java/com/jfinal/template/expr/ast/MethodKit.java
Normal file
393
src/main/java/com/jfinal/template/expr/ast/MethodKit.java
Normal file
@@ -0,0 +1,393 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
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.template.ext.extensionmethod.ByteExt;
|
||||
import com.jfinal.template.ext.extensionmethod.DoubleExt;
|
||||
import com.jfinal.template.ext.extensionmethod.FloatExt;
|
||||
import com.jfinal.template.ext.extensionmethod.IntegerExt;
|
||||
import com.jfinal.template.ext.extensionmethod.LongExt;
|
||||
import com.jfinal.template.ext.extensionmethod.ShortExt;
|
||||
import com.jfinal.template.ext.extensionmethod.StringExt;
|
||||
|
||||
/**
|
||||
* MethodKit
|
||||
*/
|
||||
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>();
|
||||
|
||||
// 初始化在模板中调用 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
|
||||
};
|
||||
for (Class<?> c : cs) {
|
||||
forbiddenClasses.add(c);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化在模板中被禁止使用的 method name
|
||||
static {
|
||||
String[] ms = {
|
||||
"getClass", "getDeclaringClass", "forName", "newInstance", "getClassLoader",
|
||||
"getMethod", "getMethods", "getField", "getFields",
|
||||
"notify", "notifyAll", "wait",
|
||||
"load", "exit", "loadLibrary", "halt",
|
||||
"stop", "suspend", "resume", "setDaemon", "setPriority",
|
||||
};
|
||||
for (String m : ms) {
|
||||
forbiddenMethods.add(m);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化 primitive type 与 boxed type 双向映射关系
|
||||
static {
|
||||
primitiveMap.put(byte.class, Byte.class);
|
||||
primitiveMap.put(short.class, Short.class);
|
||||
primitiveMap.put(int.class, Integer.class);
|
||||
primitiveMap.put(long.class, Long.class);
|
||||
primitiveMap.put(float.class, Float.class);
|
||||
primitiveMap.put(double.class, Double.class);
|
||||
primitiveMap.put(char.class, Character.class);
|
||||
primitiveMap.put(boolean.class, Boolean.class);
|
||||
|
||||
primitiveMap.put(Byte.class, byte.class);
|
||||
primitiveMap.put(Short.class, short.class);
|
||||
primitiveMap.put(Integer.class, int.class);
|
||||
primitiveMap.put(Long.class, long.class);
|
||||
primitiveMap.put(Float.class, float.class);
|
||||
primitiveMap.put(Double.class, double.class);
|
||||
primitiveMap.put(Character.class, char.class);
|
||||
primitiveMap.put(Boolean.class, boolean.class);
|
||||
}
|
||||
|
||||
public static boolean isForbiddenClass(Class<?> clazz) {
|
||||
return forbiddenClasses.contains(clazz);
|
||||
}
|
||||
|
||||
public static boolean isForbiddenMethod(String methodName) {
|
||||
return forbiddenMethods.contains(methodName);
|
||||
}
|
||||
|
||||
public static void addForbiddenMethod(String methodName) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 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);
|
||||
}
|
||||
}
|
||||
return getterMethod instanceof MethodInfo ? (MethodInfo)getterMethod : null;
|
||||
}
|
||||
|
||||
static Class<?>[] getArgTypes(Object[] argValues) {
|
||||
if (argValues == null || argValues.length == 0) {
|
||||
return NULL_ARG_TYPES;
|
||||
}
|
||||
Class<?>[] argTypes = new Class<?>[argValues.length];
|
||||
for (int i=0; i<argValues.length; i++) {
|
||||
argTypes[i] = argValues[i] != null ? argValues[i].getClass() : null;
|
||||
}
|
||||
return argTypes;
|
||||
}
|
||||
|
||||
private static MethodInfo doGetMethod(String key, Class<?> targetClass, String methodName, Class<?>[] argTypes) {
|
||||
if (forbiddenClasses.contains(targetClass)) {
|
||||
throw new RuntimeException("Forbidden class: " + targetClass.getName());
|
||||
}
|
||||
// 仅开启 forbiddenClasses 检测
|
||||
// if (forbiddenMethods.contains(methodName)) {
|
||||
// throw new RuntimeException("Forbidden method: " + methodName);
|
||||
// }
|
||||
|
||||
Method[] methodArray = targetClass.getMethods();
|
||||
for (Method method : methodArray) {
|
||||
if (method.getName().equals(methodName)) {
|
||||
Class<?>[] paraTypes = method.getParameterTypes();
|
||||
if (matchFixedArgTypes(paraTypes, argTypes)) { // 无条件优先匹配固定参数方法
|
||||
return new MethodInfo(key, targetClass, method);
|
||||
}
|
||||
if (method.isVarArgs() && matchVarArgTypes(paraTypes, argTypes)) {
|
||||
return new MethodInfo(key, targetClass, method);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static boolean matchFixedArgTypes(Class<?>[] paraTypes, Class<?>[] argTypes) {
|
||||
if (paraTypes.length != argTypes.length) {
|
||||
return false;
|
||||
}
|
||||
return matchRangeTypes(paraTypes, argTypes, paraTypes.length);
|
||||
}
|
||||
|
||||
private static boolean matchRangeTypes(Class<?>[] paraTypes, Class<?>[] argTypes, int matchLength) {
|
||||
for (int i=0; i<matchLength; i++) {
|
||||
if (argTypes[i] == null) {
|
||||
if (paraTypes[i].isPrimitive()) {
|
||||
return false;
|
||||
}
|
||||
continue ;
|
||||
}
|
||||
if (paraTypes[i].isAssignableFrom(argTypes[i])) {
|
||||
continue ;
|
||||
}
|
||||
// object instanceof Xxx、Class.isAssignableFrom(Class)、Class.isInstance(Object) not works for primitive type
|
||||
if (paraTypes[i] == argTypes[i] || primitiveMap.get(paraTypes[i]) == argTypes[i]) {
|
||||
continue ;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean matchVarArgTypes(Class<?>[] paraTypes, Class<?>[] argTypes) {
|
||||
int fixedParaLength = paraTypes.length - 1;
|
||||
if (argTypes.length < fixedParaLength) {
|
||||
return false;
|
||||
}
|
||||
if (!matchRangeTypes(paraTypes, argTypes, fixedParaLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Class<?> varArgType = paraTypes[paraTypes.length - 1].getComponentType();
|
||||
for (int i=fixedParaLength; i<argTypes.length; i++) {
|
||||
if (argTypes[i] == null) {
|
||||
if (varArgType.isPrimitive()) {
|
||||
return false;
|
||||
}
|
||||
continue ;
|
||||
}
|
||||
if (varArgType.isAssignableFrom(argTypes[i])) {
|
||||
continue ;
|
||||
}
|
||||
if (varArgType == argTypes[i] || primitiveMap.get(varArgType) == argTypes[i]) {
|
||||
continue ;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取方法用于缓存的 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()));
|
||||
}
|
||||
|
||||
// 以下代码实现 extension method 功能 --------------------
|
||||
|
||||
// 添加 jfinal 官方扩展方法 extension method
|
||||
static {
|
||||
addExtensionMethod(String.class, new StringExt());
|
||||
addExtensionMethod(Integer.class, new IntegerExt());
|
||||
addExtensionMethod(Long.class, new LongExt());
|
||||
addExtensionMethod(Float.class, new FloatExt());
|
||||
addExtensionMethod(Double.class, new DoubleExt());
|
||||
addExtensionMethod(Short.class, new ShortExt());
|
||||
addExtensionMethod(Byte.class, new ByteExt());
|
||||
}
|
||||
|
||||
public synchronized static void addExtensionMethod(Class<?> targetClass, Object objectOfExtensionClass) {
|
||||
Class<?> extensionClass = objectOfExtensionClass.getClass();
|
||||
java.lang.reflect.Method[] methodArray = extensionClass.getMethods();
|
||||
for (java.lang.reflect.Method method : methodArray) {
|
||||
Class<?> decClass = method.getDeclaringClass();
|
||||
if (decClass == Object.class) { // 考虑用于优化路由生成那段代码
|
||||
continue ;
|
||||
}
|
||||
|
||||
Class<?>[] extensionMethodParaTypes = method.getParameterTypes();
|
||||
String methodName = method.getName();
|
||||
if (extensionMethodParaTypes.length == 0) {
|
||||
throw new RuntimeException(buildMethodSignatureForException("Extension method requires at least one argument: " + extensionClass.getName() + ".", methodName, extensionMethodParaTypes));
|
||||
}
|
||||
|
||||
// Extension method 第一个参数必须与当前对象的类型一致,在调用时会将当前对象自身传给扩展方法的第一个参数
|
||||
if (targetClass != extensionMethodParaTypes[0]) {
|
||||
throw new RuntimeException(buildMethodSignatureForException("The first argument type of : " + extensionClass.getName() + ".", methodName, extensionMethodParaTypes) + " must be: " + targetClass.getName());
|
||||
}
|
||||
|
||||
Class<?>[] targetParaTypes = new Class<?>[extensionMethodParaTypes.length - 1];
|
||||
System.arraycopy(extensionMethodParaTypes, 1, targetParaTypes, 0, targetParaTypes.length);
|
||||
|
||||
try {
|
||||
Method error = targetClass.getMethod(methodName, targetParaTypes);
|
||||
if (error != null) {
|
||||
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));
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void addExtensionMethod(Class<?> targetClass, Class<?> extensionClass) {
|
||||
addExtensionMethod(targetClass, ReflectKit.newInstance(extensionClass));
|
||||
}
|
||||
|
||||
public static void removeExtensionMethod(Class<?> targetClass, Object objectOfExtensionClass) {
|
||||
Class<?> extensionClass = objectOfExtensionClass.getClass();
|
||||
java.lang.reflect.Method[] methodArray = extensionClass.getMethods();
|
||||
for (java.lang.reflect.Method method : methodArray) {
|
||||
Class<?> decClass = method.getDeclaringClass();
|
||||
if (decClass == Object.class) { // 考虑用于优化路由生成那段代码
|
||||
continue ;
|
||||
}
|
||||
|
||||
Class<?>[] extensionMethodParaTypes = method.getParameterTypes();
|
||||
String methodName = method.getName();
|
||||
Class<?>[] targetParaTypes = new Class<?>[extensionMethodParaTypes.length - 1];
|
||||
System.arraycopy(extensionMethodParaTypes, 1, targetParaTypes, 0, targetParaTypes.length);
|
||||
|
||||
String key = MethodKit.getMethodKey(targetClass, methodName, toBoxedType(targetParaTypes));
|
||||
methodCache.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Map<Class<?>, Class<?>> primitiveToBoxedMap = new HashMap<Class<?>, Class<?>>();
|
||||
|
||||
// 初始化 primitive type 到 boxed type 的映射
|
||||
static {
|
||||
primitiveToBoxedMap.put(byte.class, Byte.class);
|
||||
primitiveToBoxedMap.put(short.class, Short.class);
|
||||
primitiveToBoxedMap.put(int.class, Integer.class);
|
||||
primitiveToBoxedMap.put(long.class, Long.class);
|
||||
primitiveToBoxedMap.put(float.class, Float.class);
|
||||
primitiveToBoxedMap.put(double.class, Double.class);
|
||||
primitiveToBoxedMap.put(char.class, Character.class);
|
||||
primitiveToBoxedMap.put(boolean.class, Boolean.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 由于从在模板中传递的基本数据类型参数只可能是 boxed 类型,当 extension method 中的方法参数是
|
||||
* primitive 类型时,在 getMethod(key) 时无法获取 addExtensionMethod(...) 注册的扩展方法
|
||||
* 所以为扩展方法调用 getMethodKey(...) 生成 key 时一律转成 boxed 类型去生成方法的 key 值
|
||||
*
|
||||
* 注意:该值仅用于在获取方法是通过 key 能获取到 MethindInfoExt,而 MethindInfoExt.paraType 仍然
|
||||
* 是原来的参数值
|
||||
*/
|
||||
private static Class<?>[] toBoxedType(Class<?>[] targetParaTypes) {
|
||||
int len = targetParaTypes.length;
|
||||
if (len == 0) {
|
||||
return targetParaTypes;
|
||||
}
|
||||
|
||||
Class<?>[] ret = new Class<?>[len];
|
||||
for (int i=0; i<len; i++) {
|
||||
Class<?> temp = primitiveToBoxedMap.get(targetParaTypes[i]);
|
||||
if (temp != null) {
|
||||
ret[i] = temp;
|
||||
} else {
|
||||
ret[i] = targetParaTypes[i];
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void removeExtensionMethod(Class<?> targetClass, Class<?> extensionClass) {
|
||||
removeExtensionMethod(targetClass, ReflectKit.newInstance(extensionClass));
|
||||
}
|
||||
|
||||
private static String buildMethodSignatureForException(String preMsg, String methodName, Class<?>[] argTypes) {
|
||||
StringBuilder ret = new StringBuilder().append(preMsg).append(methodName).append("(");
|
||||
if (argTypes != null) {
|
||||
for (int i = 0; i < argTypes.length; i++) {
|
||||
if (i > 0) {
|
||||
ret.append(", ");
|
||||
}
|
||||
ret.append(argTypes[i] != null ? argTypes[i].getName() : "null");
|
||||
}
|
||||
}
|
||||
return ret.append(")").toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
68
src/main/java/com/jfinal/template/expr/ast/NullSafe.java
Normal file
68
src/main/java/com/jfinal/template/expr/ast/NullSafe.java
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import com.jfinal.template.stat.Ctrl;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* NullSafe
|
||||
* 在原则上只支持具有动态特征的用法,例如:方法调用、字段取值、Map 与 List 取值
|
||||
* 而不支持具有静态特征的用法,例如:static method 调用、shared method 调用
|
||||
*
|
||||
* 用法:
|
||||
* #( seoTitle ?? "JFinal 极速开发社区" )
|
||||
* 支持级联: #( a.b.c ?? "JFinal 极速开发社区" )
|
||||
* 支持嵌套: #( a ?? b ?? c ?? d)
|
||||
*/
|
||||
public class NullSafe extends Expr {
|
||||
|
||||
private Expr left;
|
||||
private Expr right;
|
||||
|
||||
public NullSafe(Expr left, Expr right, Location location) {
|
||||
if (left == null) {
|
||||
throw new ParseException("The expression on the left side of null coalescing and safe access operator \"??\" can not be blank", location);
|
||||
}
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
Ctrl ctrl = scope.getCtrl();
|
||||
boolean oldNullSafeValue = ctrl.isNullSafe();
|
||||
|
||||
Object ret;
|
||||
try {
|
||||
ctrl.setNullSafe(true);
|
||||
ret = left.eval(scope);
|
||||
} finally {
|
||||
ctrl.setNullSafe(oldNullSafeValue);
|
||||
}
|
||||
|
||||
return ret == null && right != null ? right.eval(scope) : ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
96
src/main/java/com/jfinal/template/expr/ast/RangeArray.java
Normal file
96
src/main/java/com/jfinal/template/expr/ast/RangeArray.java
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* RangeArray : [expr .. expr]
|
||||
*
|
||||
* 用法:
|
||||
* 1:[1..3]
|
||||
* 2:[3..1]
|
||||
*/
|
||||
public class RangeArray extends Expr {
|
||||
|
||||
private Expr start;
|
||||
private Expr end;
|
||||
|
||||
/**
|
||||
* array : '[' exprList ? | range ? ']'
|
||||
* exprList : expr (',' expr)*
|
||||
* range : expr .. expr
|
||||
*/
|
||||
public RangeArray(Expr start, Expr end, Location location) {
|
||||
if (start == null) {
|
||||
throw new ParseException("The start value of range array can not be blank", location);
|
||||
}
|
||||
if (end == null) {
|
||||
throw new ParseException("The end value of range array can not be blank", location);
|
||||
}
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
Object startValue = start.eval(scope);
|
||||
if ( !(startValue instanceof Integer) ) {
|
||||
throw new TemplateException("The start value of range array must be Integer", location);
|
||||
}
|
||||
Object endValue = end.eval(scope);
|
||||
if ( !(endValue instanceof Integer) ) {
|
||||
throw new TemplateException("The end value of range array must be Integer", location);
|
||||
}
|
||||
|
||||
return new RangeList((Integer)startValue, (Integer)endValue, location);
|
||||
}
|
||||
|
||||
public static class RangeList extends AbstractList<Integer> {
|
||||
|
||||
final int start;
|
||||
final int size;
|
||||
final int increment;
|
||||
final Location location;
|
||||
|
||||
public RangeList(int start, int end, Location location) {
|
||||
this.start = start;
|
||||
this.increment = start <= end ? 1 : -1;
|
||||
this.size = Math.abs(end - start) + 1;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Integer get(int index) {
|
||||
if (index < 0 || index >= size) {
|
||||
throw new TemplateException("Index out of bounds. Index: " + index + ", Size: " + size, location);
|
||||
}
|
||||
return start + index * increment;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
67
src/main/java/com/jfinal/template/expr/ast/SharedMethod.java
Normal file
67
src/main/java/com/jfinal/template/expr/ast/SharedMethod.java
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.expr.ast.SharedMethodKit.SharedMethodInfo;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* SharedMethod
|
||||
*
|
||||
* 用法:
|
||||
* engine.addSharedMethod(object);
|
||||
* engine.addSharedStaticMethod(Xxx.class);
|
||||
* #(method(para))
|
||||
*/
|
||||
public class SharedMethod extends Expr {
|
||||
|
||||
private SharedMethodKit sharedMethodKit;
|
||||
private String methodName;
|
||||
private ExprList exprList;
|
||||
|
||||
public SharedMethod(SharedMethodKit sharedMethodKit, String methodName, ExprList exprList, Location location) {
|
||||
if (MethodKit.isForbiddenMethod(methodName)) {
|
||||
throw new ParseException("Forbidden method: " + methodName, location);
|
||||
}
|
||||
this.sharedMethodKit = sharedMethodKit;
|
||||
this.methodName = methodName;
|
||||
this.exprList = exprList;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
Object[] argValues = exprList.evalExprList(scope);
|
||||
SharedMethodInfo sharedMethodInfo = sharedMethodKit.getSharedMethodInfo(methodName, argValues);
|
||||
|
||||
// ShareMethod 相当于是固定的静态的方法,不支持 null safe,null safe 只支持具有动态特征的用法
|
||||
if (sharedMethodInfo == null) {
|
||||
throw new TemplateException(Method.buildMethodNotFoundSignature("Shared method not found: ", methodName, argValues), location);
|
||||
}
|
||||
try {
|
||||
return sharedMethodInfo.invoke(argValues);
|
||||
} catch (Exception e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
173
src/main/java/com/jfinal/template/expr/ast/SharedMethodKit.java
Normal file
173
src/main/java/com/jfinal/template/expr/ast/SharedMethodKit.java
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import com.jfinal.kit.ReflectKit;
|
||||
|
||||
/**
|
||||
* SharedMethodKit
|
||||
*/
|
||||
public class SharedMethodKit {
|
||||
|
||||
private static final Set<String> excludedMethodKey = new HashSet<String>();
|
||||
|
||||
static {
|
||||
Method[] methods = Object.class.getMethods();
|
||||
for (Method method : methods) {
|
||||
String 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>();
|
||||
|
||||
public SharedMethodInfo getSharedMethodInfo(String methodName, Object[] argValues) {
|
||||
Class<?>[] argTypes = MethodKit.getArgTypes(argValues);
|
||||
String 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)
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
private SharedMethodInfo doGetSharedMethodInfo(String methodName, Class<?>[] argTypes) {
|
||||
for (SharedMethodInfo smi : sharedMethodList) {
|
||||
if (smi.getName().equals(methodName)) {
|
||||
Class<?>[] paraTypes = smi.getParameterTypes();
|
||||
if (MethodKit.matchFixedArgTypes(paraTypes, argTypes)) { // 无条件优先匹配固定参数方法
|
||||
return smi;
|
||||
}
|
||||
if (smi.isVarArgs() && MethodKit.matchVarArgTypes(paraTypes, argTypes)) {
|
||||
return smi;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addSharedMethod(Object sharedMethodFromObject) {
|
||||
addSharedMethod(sharedMethodFromObject.getClass(), sharedMethodFromObject);
|
||||
}
|
||||
|
||||
public void addSharedMethod(Class<?> sharedMethodFromClass) {
|
||||
addSharedMethod(sharedMethodFromClass, ReflectKit.newInstance(sharedMethodFromClass));
|
||||
}
|
||||
|
||||
public void addSharedStaticMethod(Class<?> sharedStaticMethodFromClass) {
|
||||
addSharedMethod(sharedStaticMethodFromClass, null);
|
||||
}
|
||||
|
||||
public void removeSharedMethod(String methodName) {
|
||||
Iterator<SharedMethodInfo> it = sharedMethodList.iterator();
|
||||
while(it.hasNext()) {
|
||||
if (it.next().getName().equals(methodName)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeSharedMethod(Class<?> sharedClass) {
|
||||
Iterator<SharedMethodInfo> it = sharedMethodList.iterator();
|
||||
while(it.hasNext()) {
|
||||
if (it.next().getClazz() == sharedClass) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeSharedMethod(Method method) {
|
||||
Iterator<SharedMethodInfo> it = sharedMethodList.iterator();
|
||||
while(it.hasNext()) {
|
||||
SharedMethodInfo current = it.next();
|
||||
String methodName = method.getName();
|
||||
if (current.getName().equals(methodName)) {
|
||||
String key = getSharedMethodKey(methodName, method.getParameterTypes());
|
||||
if (current.getKey().equals(key)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void addSharedMethod(Class<?> sharedClass, Object target) {
|
||||
if (MethodKit.isForbiddenClass(sharedClass)) {
|
||||
throw new IllegalArgumentException("Forbidden class: " + sharedClass.getName());
|
||||
}
|
||||
|
||||
Method[] methods = sharedClass.getMethods();
|
||||
for (Method method : methods) {
|
||||
String key = getSharedMethodKey(method.getName(), method.getParameterTypes());
|
||||
if (excludedMethodKey.contains(key)) {
|
||||
continue ;
|
||||
}
|
||||
|
||||
for (SharedMethodInfo smi : sharedMethodList) {
|
||||
if (smi.getKey().equals(key)) {
|
||||
throw new RuntimeException("The shared method is already exists : " + smi.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (target != null) {
|
||||
sharedMethodList.add(new SharedMethodInfo(key, sharedClass, method, target));
|
||||
} else if (Modifier.isStatic(method.getModifiers())) { // target 为 null 时添加 static method
|
||||
sharedMethodList.add(new SharedMethodInfo(key, sharedClass, method, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return key.toString();
|
||||
}
|
||||
|
||||
static class SharedMethodInfo extends MethodInfo {
|
||||
final Object target;
|
||||
|
||||
private SharedMethodInfo(String key, Class<?> clazz, Method method, Object target) {
|
||||
super(key, clazz, method);
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public Object invoke(Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||
return super.invoke(target, args);
|
||||
}
|
||||
|
||||
Class<?> getClazz() {
|
||||
return clazz;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
65
src/main/java/com/jfinal/template/expr/ast/StaticField.java
Normal file
65
src/main/java/com/jfinal/template/expr/ast/StaticField.java
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* StaticField : ID_list '::' ID
|
||||
* 动态获取静态变量值,变量值改变时仍可正确获取
|
||||
* 用法:com.jfinal.core.Const::JFINAL_VERSION
|
||||
*/
|
||||
public class StaticField extends Expr {
|
||||
|
||||
private Class<?> clazz;
|
||||
private String fieldName;
|
||||
private Field field;
|
||||
|
||||
public StaticField(String className, String fieldName, Location location) {
|
||||
try {
|
||||
this.clazz = Class.forName(className);
|
||||
this.fieldName = fieldName;
|
||||
this.field = clazz.getField(fieldName);
|
||||
this.location = location;
|
||||
} catch (Exception e) {
|
||||
throw new ParseException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
try {
|
||||
return field.get(null);
|
||||
} catch (Exception e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return clazz.getName() + "::" + fieldName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
85
src/main/java/com/jfinal/template/expr/ast/StaticMethod.java
Normal file
85
src/main/java/com/jfinal/template/expr/ast/StaticMethod.java
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* StaticMethod : ID_list : '::' ID '(' exprList? ')'
|
||||
* 用法: com.jfinal.kit.Str::isBlank("abc")
|
||||
*/
|
||||
public class StaticMethod extends Expr {
|
||||
|
||||
private Class<?> clazz;
|
||||
private String methodName;
|
||||
private ExprList exprList;
|
||||
|
||||
public StaticMethod(String className, String methodName, Location location) {
|
||||
init(className, methodName, ExprList.NULL_EXPR_LIST, location);
|
||||
}
|
||||
|
||||
public StaticMethod(String className, String methodName, ExprList exprList, Location location) {
|
||||
if (exprList == null || exprList.length() == 0) {
|
||||
throw new ParseException("exprList can not be blank", location);
|
||||
}
|
||||
init(className, methodName, exprList, location);
|
||||
}
|
||||
|
||||
private void init(String className, String methodName, ExprList exprList, Location location) {
|
||||
try {
|
||||
this.clazz = Class.forName(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new ParseException("Class not found: " + className, location, e);
|
||||
} catch (Exception e) {
|
||||
throw new ParseException(e.getMessage(), location, e);
|
||||
}
|
||||
this.methodName = methodName;
|
||||
this.exprList = exprList;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
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 safe,null 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);
|
||||
} catch (Exception e) {
|
||||
throw new TemplateException(e.getMessage(), location, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
56
src/main/java/com/jfinal/template/expr/ast/Ternary.java
Normal file
56
src/main/java/com/jfinal/template/expr/ast/Ternary.java
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* Ternary
|
||||
*/
|
||||
public class Ternary extends Expr {
|
||||
|
||||
private Expr cond;
|
||||
private Expr exprOne;
|
||||
private Expr exprTwo;
|
||||
|
||||
/**
|
||||
* cond ? exprOne : exprTwo
|
||||
*/
|
||||
public Ternary(Expr cond, Expr exprOne, Expr exprTwo, Location location) {
|
||||
if (cond == null || exprOne == null || exprTwo == null) {
|
||||
throw new ParseException("The parameter of ternary expression can not be blank", location);
|
||||
}
|
||||
this.cond = cond;
|
||||
this.exprOne = exprOne;
|
||||
this.exprTwo = exprTwo;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Object eval(Scope scope) {
|
||||
return Logic.isTrue(cond.eval(scope)) ? exprOne.eval(scope) : exprTwo.eval(scope);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
91
src/main/java/com/jfinal/template/expr/ast/Unary.java
Normal file
91
src/main/java/com/jfinal/template/expr/ast/Unary.java
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* 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.expr.ast;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import com.jfinal.template.TemplateException;
|
||||
import com.jfinal.template.expr.Sym;
|
||||
import com.jfinal.template.stat.Location;
|
||||
import com.jfinal.template.stat.ParseException;
|
||||
import com.jfinal.template.stat.Scope;
|
||||
|
||||
/**
|
||||
* unary : ('!' | '+' | '-'| '++' | '--') expr
|
||||
*
|
||||
* 只支持 +expr 与 -expr
|
||||
* !expr、 ++expr、 --expr 分别由 Logic、IncDec 支持
|
||||
*/
|
||||
public class Unary extends Expr {
|
||||
|
||||
private Sym op;
|
||||
private Expr expr;
|
||||
|
||||
public Unary(Sym op, Expr expr, Location location) {
|
||||
if (expr == null) {
|
||||
throw new ParseException("The parameter of \"" + op.value() + "\" operator can not be blank", location);
|
||||
}
|
||||
this.op = op;
|
||||
this.expr = expr;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
/**
|
||||
* unary : ('!' | '+' | '-'| '++' | '--') expr
|
||||
*/
|
||||
public Object eval(Scope scope) {
|
||||
Object value = expr.eval(scope);
|
||||
if (value == null) {
|
||||
if (scope.getCtrl().isNullSafe()) {
|
||||
return null;
|
||||
}
|
||||
throw new TemplateException("The parameter of \"" + op.value() + "\" operator can not be blank", location);
|
||||
}
|
||||
if (! (value instanceof Number) ) {
|
||||
throw new TemplateException(op.value() + " operator only support int long float double BigDecimal type", location);
|
||||
}
|
||||
|
||||
switch (op) {
|
||||
case ADD:
|
||||
return value;
|
||||
case SUB:
|
||||
Number n = (Number)value;
|
||||
if (n instanceof Integer) {
|
||||
return Integer.valueOf(-n.intValue());
|
||||
}
|
||||
if (n instanceof Long) {
|
||||
return Long.valueOf(-n.longValue());
|
||||
}
|
||||
if (n instanceof Float) {
|
||||
return Float.valueOf(-n.floatValue());
|
||||
}
|
||||
if (n instanceof Double) {
|
||||
return Double.valueOf(-n.doubleValue());
|
||||
}
|
||||
if (n instanceof BigDecimal) {
|
||||
return ((BigDecimal)n).negate();
|
||||
}
|
||||
throw new TemplateException("Unsupported data type: " + n.getClass().getName(), location);
|
||||
default :
|
||||
throw new TemplateException("Unsupported operator: " + op.value(), location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user