/** * 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.ast; import org.mvel2.CompileException; import org.mvel2.Operator; import org.mvel2.ParserContext; import org.mvel2.ScriptRuntimeException; import org.mvel2.integration.VariableResolverFactory; import org.mvel2.util.CompatibilityStrategy; import org.mvel2.util.NullType; import org.mvel2.util.ParseTools; import static org.mvel2.DataConversion.canConvert; import static org.mvel2.DataConversion.convert; import static org.mvel2.Operator.PTABLE; import static org.mvel2.debug.DebugTools.getOperatorSymbol; import static org.mvel2.math.MathProcessor.doOperations; import static org.mvel2.util.CompilerTools.getReturnTypeFromOp; import static org.mvel2.util.ParseTools.boxPrimitive; public class BinaryOperation extends BooleanNode { private final int operation; private int lType = -1; private int rType = -1; public BinaryOperation(int operation, ParserContext ctx) { super(ctx); this.operation = operation; } public BinaryOperation(int operation, ASTNode left, ASTNode right, ParserContext ctx) { super(ctx); this.operation = operation; if ((this.left = left) == null) { throw new ScriptRuntimeException("not a statement"); } if ((this.right = right) == null) { throw new ScriptRuntimeException("not a statement"); } // if (ctx.isStrongTyping()) { switch (operation) { case Operator.ADD: /** * In the special case of Strings, the return type may leftward propogate. */ if (left.getEgressType() == String.class || right.getEgressType() == String.class) { egressType = String.class; lType = ParseTools.__resolveType(left.egressType); rType = ParseTools.__resolveType(right.egressType); return; } default: egressType = getReturnTypeFromOp(operation, this.left.egressType, this.right.egressType); if (!ctx.isStrongTyping()) break; if (!left.getEgressType().isAssignableFrom(right.getEgressType()) && !right.getEgressType().isAssignableFrom(left.getEgressType())) { if (right.isLiteral() && canConvert(left.getEgressType(), right.getEgressType())) { Class targetType = isAritmeticOperation(operation) ? egressType : left.getEgressType(); this.right = new LiteralNode(convert(right.getReducedValueAccelerated(null, null, null), targetType), pCtx); } else if ( !(areCompatible(left.getEgressType(), right.getEgressType()) || (( operation == Operator.EQUAL || operation == Operator.NEQUAL) && CompatibilityStrategy.areEqualityCompatible(left.getEgressType(), right.getEgressType()))) ) { throw new CompileException("incompatible types in statement: " + right.getEgressType() + " (compared from: " + left.getEgressType() + ")", left.getExpr() != null ? left.getExpr() : right.getExpr(), left.getExpr() != null ? left.getStart() : right.getStart()); } } } // } if (this.left.isLiteral() && this.right.isLiteral()) { if (this.left.egressType == this.right.egressType) { lType = rType = ParseTools.__resolveType(left.egressType); } else { lType = ParseTools.__resolveType(this.left.egressType); rType = ParseTools.__resolveType(this.right.egressType); } } } private boolean isAritmeticOperation(int operation) { return operation <= Operator.POWER; } private boolean areCompatible(Class<?> leftClass, Class<?> rightClass) { return leftClass.equals(NullType.class) || rightClass.equals(NullType.class) || ( Number.class.isAssignableFrom(rightClass) && Number.class.isAssignableFrom(leftClass) ) || ( (rightClass.isPrimitive() || leftClass.isPrimitive()) && canConvert(boxPrimitive(leftClass), boxPrimitive(rightClass)) ); } public Object getReducedValueAccelerated(Object ctx, Object thisValue, VariableResolverFactory factory) { return doOperations(lType, left.getReducedValueAccelerated(ctx, thisValue, factory), operation, rType, right.getReducedValueAccelerated(ctx, thisValue, factory)); } public Object getReducedValue(Object ctx, Object thisValue, VariableResolverFactory factory) { throw new RuntimeException("unsupported AST operation"); } public int getOperation() { return operation; } public BinaryOperation getRightBinary() { return right != null && right instanceof BinaryOperation ? (BinaryOperation) right : null; } public void setRightMost(ASTNode right) { BinaryOperation n = this; while (n.right != null && n.right instanceof BinaryOperation) { n = (BinaryOperation) n.right; } n.right = right; if (n == this) { if ((rType = ParseTools.__resolveType(n.right.getEgressType())) == 0) rType = -1; } } public ASTNode getRightMost() { BinaryOperation n = this; while (n.right != null && n.right instanceof BinaryOperation) { n = (BinaryOperation) n.right; } return n.right; } public int getPrecedence() { return PTABLE[operation]; } public boolean isGreaterPrecedence(BinaryOperation o) { return o.getPrecedence() > PTABLE[operation]; } @Override public boolean isLiteral() { return false; } public String toString() { return "(" + left + " " + getOperatorSymbol(operation) + " " + right + ")"; } }