org.mvel2.util.CompilerTools Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tbel Show documentation
Show all versions of tbel Show documentation
TBEL is a powerful expression language for ThingsBoard platform user-defined functions.
Original implementation is based on MVEL.
The newest version!
/**
* MVEL 2.0
* Copyright (C) 2007 The Codehaus
* Mike Brock, Dhanji Prasanna, John Graham, Mark Proctor
*
* 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 org.mvel2.util;
import org.mvel2.CompileException;
import org.mvel2.Operator;
import org.mvel2.ParserContext;
import org.mvel2.ast.ASTNode;
import org.mvel2.ast.And;
import org.mvel2.ast.BinaryOperation;
import org.mvel2.ast.BooleanNode;
import org.mvel2.ast.Contains;
import org.mvel2.ast.Convertable;
import org.mvel2.ast.DeclTypedVarNode;
import org.mvel2.ast.Function;
import org.mvel2.ast.Instance;
import org.mvel2.ast.IntAdd;
import org.mvel2.ast.IntDiv;
import org.mvel2.ast.IntMult;
import org.mvel2.ast.IntOptimized;
import org.mvel2.ast.IntSub;
import org.mvel2.ast.LiteralNode;
import org.mvel2.ast.Or;
import org.mvel2.ast.RegExMatchNode;
import org.mvel2.ast.Soundslike;
import org.mvel2.ast.Strsim;
import org.mvel2.compiler.Accessor;
import org.mvel2.compiler.BlankLiteral;
import org.mvel2.compiler.CompiledExpression;
import org.mvel2.compiler.ExecutableAccessor;
import org.mvel2.compiler.ExecutableLiteral;
import org.mvel2.integration.VariableResolverFactory;
import org.mvel2.integration.impl.ClassImportResolverFactory;
import java.util.LinkedHashMap;
import java.util.Map;
import static org.mvel2.Operator.PTABLE;
import static org.mvel2.Operator.TERNARY;
import static org.mvel2.util.ASTBinaryTree.buildTree;
import static org.mvel2.util.ParseTools.__resolveType;
import static org.mvel2.util.ParseTools.boxPrimitive;
public class CompilerTools {
/**
* Finalize the payload, by reducing any stack-based-operations to dedicated nodes where possible.
*
* @param astLinkedList - AST to be optimized.
* @param secondPassOptimization - perform a second pass optimization to optimize boolean expressions.
* @param pCtx - The parser context
* @return optimized AST
*/
public static ASTLinkedList finalizePayload(ASTLinkedList astLinkedList, boolean secondPassOptimization, ParserContext pCtx) {
ASTLinkedList optimizedAst = new ASTLinkedList();
ASTNode tk, tkOp, tkOp2;
/**
* Re-process the AST and optimize it.
*/
while (astLinkedList.hasMoreNodes()) {
if ((tk = astLinkedList.nextNode()).getFields() == -1) {
optimizedAst.addTokenNode(tk);
}
else if (astLinkedList.hasMoreNodes()) {
if ((tkOp = astLinkedList.nextNode()).getFields() == -1) {
optimizedAst.addTokenNode(tk, tkOp);
}
else if (tkOp.isOperator() && tkOp.getOperator() < 21) {
int op = tkOp.getOperator();
int op2;
if (op == -1) {
throw new CompileException("illegal use of operator: " + tkOp.getName(), tkOp.getExpr(), tk.getStart());
}
ASTNode tk2 = astLinkedList.nextNode();
if (tk2 == null) {
throw new RuntimeException("Invalid expression");
}
BinaryOperation bo;
if (tk.getEgressType() == Integer.class && tk2.getEgressType() == Integer.class) {
bo = boOptimize(op, tk, tk2, pCtx);
}
else {
/**
* Let's see if we can simply the expression more.
*/
bo = null;
boolean inv = tkOp.isOperator(Operator.SUB);
boolean reduc = tk.isLiteral() && isReductionOpportunity(tkOp, tk2);
boolean p_inv = false;
while (reduc) {
ASTNode oper = astLinkedList.nextNode();
ASTNode rightNode = astLinkedList.nextNode();
if (rightNode == null) break;
Object val = new BinaryOperation(oper.getOperator(), inv ?
new LiteralNode(signNumber(tk2.getLiteralValue()), pCtx) : tk2, rightNode, pCtx)
.getReducedValueAccelerated(null, null, null);
if (!astLinkedList.hasMoreNodes() && BlankLiteral.INSTANCE.equals(val)) {
optimizedAst.addTokenNode(tk);
continue;
}
reduc = astLinkedList.hasMoreNodes()
&& (reducacbleOperator(astLinkedList.peekNode().getOperator()))
&& astLinkedList.peekNext().isLiteral();
if (inv) p_inv = true;
inv = false;
if (!reduc) {
bo = new BinaryOperation(tkOp.getOperator(), tk, new LiteralNode(p_inv ? signNumber(val) : val, pCtx), pCtx);
}
else {
tk2 = new LiteralNode(val, pCtx);
}
}
if (bo == null)
bo = new BinaryOperation(op, tk, tk2, pCtx);
}
tkOp2 = null;
/**
* If we have a chain of math/comparitive operators then we fill them into the tree
* right here.
*/
while (astLinkedList.hasMoreNodes() && (tkOp2 = astLinkedList.nextNode()).isOperator()
&& tkOp2.getFields() != -1 && (op2 = tkOp2.getOperator()) != -1 && op2 < 21) {
if (PTABLE[op2] > PTABLE[op]) {
BinaryOperation newRightBo = boOptimize(op2, bo.getRightMost(), astLinkedList.nextNode(), pCtx);
if (isIntOptimizationviolation(bo, newRightBo)) {
// Oops! We optimized the node based on the assumed right node type but it gets replaced
bo = new BinaryOperation(bo.getOperation(), bo.getLeft(), newRightBo, pCtx);
}
else {
bo.setRightMost(newRightBo);
}
}
else if (bo.getOperation() != op2 && PTABLE[op] == PTABLE[op2]) {
if (PTABLE[bo.getOperation()] == PTABLE[op2]) {
// bo = new BinaryOperation(op2, bo, astLinkedList.nextNode(), pCtx);
bo = boOptimize(op2, bo, astLinkedList.nextNode(), pCtx);
}
else {
tk2 = astLinkedList.nextNode();
if (isIntOptimizationviolation(bo, tk2)) {
bo = new BinaryOperation(bo.getOperation(), bo.getLeft(), bo.getRight(), pCtx);
}
bo.setRight(new BinaryOperation(op2, bo.getRight(), tk2, pCtx));
}
}
else if (PTABLE[bo.getOperation()] >= PTABLE[op2]) {
bo = new BinaryOperation(op2, bo, astLinkedList.nextNode(), pCtx);
}
else {
tk2 = astLinkedList.nextNode();
if (isIntOptimizationviolation(bo, tk2)) {
bo = new BinaryOperation(bo.getOperation(), bo.getLeft(), bo.getRight(), pCtx);
}
bo.setRight(new BinaryOperation(op2, bo.getRight(), tk2, pCtx));
}
op = op2;
tkOp = tkOp2;
}
if (tkOp2 != null && tkOp2 != tkOp) {
optimizeOperator(tkOp2.getOperator(), bo, tkOp2, astLinkedList, optimizedAst, pCtx);
}
else {
optimizedAst.addTokenNode(bo);
}
}
else if (tkOp.isOperator()) {
optimizeOperator(tkOp.getOperator(), tk, tkOp, astLinkedList, optimizedAst, pCtx);
}
else if (!tkOp.isAssignment() && !tkOp.isOperator() && tk.getLiteralValue() instanceof Class) {
optimizedAst.addTokenNode(new DeclTypedVarNode(tkOp.getName(), tkOp.getExpr(), tkOp.getStart(), tk.getOffset(), (Class) tk.getLiteralValue(), 0, pCtx));
}
else if (tkOp.isAssignment() && tk.getLiteralValue() instanceof Class) {
tk.discard();
optimizedAst.addTokenNode(tkOp);
}
else if (astLinkedList.hasMoreNodes() && tkOp.getLiteralValue() instanceof Class
&& astLinkedList.peekNode().isAssignment()) {
tkOp.discard();
optimizedAst.addTokenNode(tk, astLinkedList.nextNode());
}
else {
astLinkedList.back();
optimizedAst.addTokenNode(tk);
}
}
else {
optimizedAst.addTokenNode(tk);
}
}
if (secondPassOptimization) {
/**
* Perform a second pass optimization for boolean conditions.
*/
(astLinkedList = optimizedAst).reset();
optimizedAst = new ASTLinkedList();
while (astLinkedList.hasMoreNodes()) {
if ((tk = astLinkedList.nextNode()).getFields() == -1) {
optimizedAst.addTokenNode(tk);
}
else if (astLinkedList.hasMoreNodes()) {
if ((tkOp = astLinkedList.nextNode()).getFields() == -1) {
optimizedAst.addTokenNode(tk, tkOp);
}
else if (tkOp.isOperator()
&& (tkOp.getOperator() == Operator.AND || tkOp.getOperator() == Operator.OR)) {
tkOp2 = null;
BooleanNode bool;
if (tkOp.getOperator() == Operator.AND) {
bool = new And(tk, astLinkedList.nextNode(), pCtx.isStrongTyping(), pCtx);
}
else {
bool = new Or(tk, astLinkedList.nextNode(), pCtx.isStrongTyping(), pCtx);
}
while (astLinkedList.hasMoreNodes() && (tkOp2 = astLinkedList.nextNode()).isOperator()
&& (tkOp2.isOperator(Operator.AND) || tkOp2.isOperator(Operator.OR))) {
if ((tkOp = tkOp2).getOperator() == Operator.AND) {
bool.setRightMost(new And(bool.getRightMost(), astLinkedList.nextNode(), pCtx.isStrongTyping(), pCtx));
}
else {
bool = new Or(bool, astLinkedList.nextNode(), pCtx.isStrongTyping(), pCtx);
}
}
optimizedAst.addTokenNode(bool);
if (tkOp2 != null && tkOp2 != tkOp) {
optimizedAst.addTokenNode(tkOp2);
}
}
else {
optimizedAst.addTokenNode(tk, tkOp);
}
}
else {
optimizedAst.addTokenNode(tk);
}
}
}
return optimizedAst;
}
private static BinaryOperation boOptimize(int op, ASTNode tk, ASTNode tk2, ParserContext pCtx) {
if (tk.getEgressType() == Integer.class && tk2.getEgressType() == Integer.class) {
switch (op) {
case Operator.ADD:
return new IntAdd(tk, tk2, pCtx);
case Operator.SUB:
return new IntSub(tk, tk2, pCtx);
case Operator.MULT:
return new IntMult(tk, tk2, pCtx);
case Operator.DIV:
return new IntDiv(tk, tk2, pCtx);
default:
return new BinaryOperation(op, tk, tk2, pCtx);
}
}
else {
return new BinaryOperation(op, tk, tk2, pCtx);
}
}
private static boolean isReductionOpportunity(ASTNode oper, ASTNode node) {
ASTNode n = node;
return (n != null && n.isLiteral()
&& (n = n.nextASTNode) != null && reducacbleOperator(n.getOperator())
&& PTABLE[oper.getOperator()] <= PTABLE[n.getOperator()]
&& (n = n.nextASTNode) != null && n.isLiteral() && n.getLiteralValue() instanceof Number);
}
private static boolean reducacbleOperator(int oper) {
switch (oper) {
case Operator.ADD:
case Operator.SUB:
return true;
}
return false;
}
private static void optimizeOperator(int operator, ASTNode tk, ASTNode tkOp,
ASTLinkedList astLinkedList,
ASTLinkedList optimizedAst,
ParserContext pCtx) {
switch (operator) {
case Operator.REGEX:
optimizedAst.addTokenNode(new RegExMatchNode(tk, astLinkedList.nextNode(), pCtx));
break;
case Operator.CONTAINS:
optimizedAst.addTokenNode(new Contains(tk, astLinkedList.nextNode(), pCtx));
break;
case Operator.INSTANCEOF:
optimizedAst.addTokenNode(new Instance(tk, astLinkedList.nextNode(), pCtx));
break;
case Operator.CONVERTABLE_TO:
optimizedAst.addTokenNode((new Convertable(tk, astLinkedList.nextNode(), pCtx)));
break;
case Operator.SIMILARITY:
optimizedAst.addTokenNode(new Strsim(tk, astLinkedList.nextNode(), pCtx));
break;
case Operator.SOUNDEX:
optimizedAst.addTokenNode(new Soundslike(tk, astLinkedList.nextNode(), pCtx));
break;
case TERNARY:
if ( pCtx.isStrongTyping() && tk.getEgressType() != Boolean.class && tk.getEgressType() != Boolean.TYPE )
throw new RuntimeException( "Condition of ternary operator is not of type boolean. Found " + tk.getEgressType() );
default:
optimizedAst.addTokenNode(tk, tkOp);
}
}
private static boolean isIntOptimizationviolation(BooleanNode bn, ASTNode bn2) {
return (bn instanceof IntOptimized && bn2.getEgressType() != Integer.class);
}
public static Class getReturnType(ASTIterator input, boolean strongTyping) {
ASTNode begin = input.firstNode();
if (begin == null) return Object.class;
if (input.size() == 1) return begin.getEgressType();
return buildTree(input).getReturnType(strongTyping);
}
/**
* Returns an ordered Map of all functions declared within an compiled script.
*
* @param compile compile
* @return - ordered Map
*/
public static Map extractAllDeclaredFunctions(CompiledExpression compile) {
Map allFunctions = new LinkedHashMap();
ASTIterator instructions = new ASTLinkedList(compile.getFirstNode());
ASTNode n;
while (instructions.hasMoreNodes()) {
if ((n = instructions.nextNode()) instanceof Function) {
allFunctions.put(n.getName(), (Function) n);
}
}
return allFunctions;
}
public static void expectType(ParserContext pCtx, Accessor expression, Class type, boolean compileMode) {
Class retTypeEg = expression.getKnownEgressType();
Class retType = boxPrimitive(retTypeEg).isAssignableFrom(boxPrimitive(type)) ? type : retTypeEg;
if (compileMode) {
if ((retType == null || !boxPrimitive(type).isAssignableFrom(boxPrimitive(retType))) && (!Object.class.equals(retType)
|| pCtx.isStrictTypeEnforcement())) {
throw new CompileException("was expecting type: " + type.getName() + "; but found type: "
+ (retType != null ? retType.getName() : ""), new char[0], 0);
}
} else if (retType == null || !Object.class.equals(retType) && !boxPrimitive(type).isAssignableFrom(boxPrimitive(retType))) {
throw new CompileException("was expecting type: " + type.getName() + "; but found type: "
+ (retType != null ? retType.getName() : ""), new char[0], 0);
}
}
public static void expectType(ParserContext pCtx, ASTNode node, Class type, boolean compileMode) {
Class retTypeEg = boxPrimitive(node.getEgressType());
Class retType = boxPrimitive(retTypeEg).isAssignableFrom(boxPrimitive(type)) ? type : retTypeEg;
if (compileMode) {
if ((retType == null || !boxPrimitive(type).isAssignableFrom(retType)) && (!Object.class.equals(retType) && pCtx.isStrictTypeEnforcement())) {
throw new CompileException("was expecting type: " + type.getName() + "; but found type: "
+ (retType != null ? retType.getName() : ""), new char[0], 0);
}
} else if (retType == null || !Object.class.equals(retType) && !boxPrimitive(type).isAssignableFrom(retType)) {
throw new CompileException("was expecting type: " + type.getName() + "; but found type: "
+ (retType != null ? retType.getName() : ""), new char[0], 0);
}
}
public static Class getReturnTypeFromOp(int operation, Class left, Class right) {
switch (operation) {
case Operator.LETHAN:
case Operator.LTHAN:
case Operator.GETHAN:
case Operator.GTHAN:
case Operator.EQUAL:
case Operator.NEQUAL:
case Operator.AND:
case Operator.OR:
case Operator.CONTAINS:
case Operator.CONVERTABLE_TO:
return Boolean.class;
case Operator.ADD:
if (left == String.class) return String.class;
case Operator.SUB:
case Operator.MULT:
case Operator.POWER:
case Operator.MOD:
case Operator.DIV:
if (left == Object.class || right == Object.class)
return Object.class;
else
return __resolveType(boxPrimitive(left)) < __resolveType(boxPrimitive(right)) ? right : left;
case Operator.BW_AND:
case Operator.BW_OR:
case Operator.BW_XOR:
case Operator.BW_SHIFT_RIGHT:
case Operator.BW_SHIFT_LEFT:
case Operator.BW_USHIFT_LEFT:
case Operator.BW_USHIFT_RIGHT:
case Operator.BW_NOT:
return Integer.class;
case Operator.STR_APPEND:
return String.class;
}
return null;
}
public static Accessor extractAccessor(ASTNode n) {
if (n instanceof LiteralNode) return new ExecutableLiteral(n.getLiteralValue());
else return new ExecutableAccessor(n, n.getEgressType());
}
public static Map getInjectedImports(VariableResolverFactory factory) {
if (factory == null) return null;
do {
if (factory instanceof ClassImportResolverFactory) {
return ((ClassImportResolverFactory) factory).getImportedClasses();
}
}
while ((factory = factory.getNextFactory()) != null);
return null;
}
public static Number signNumber(Object number) {
if (number instanceof Integer) {
return -((Integer) number);
}
else if (number instanceof Double) {
return -((Double) number);
}
else if (number instanceof Float) {
return -((Float) number);
}
else if (number instanceof Short) {
return -((Short) number);
}
else {
throw new CompileException("expected a numeric type but found: " + number.getClass().getName(), new char[0], 0);
}
}
}