/*
* Copyright 2002-2017 the original author or authors.
*
* 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.springframework.expression.spel.ast;
import java.math.BigDecimal;
import java.math.BigInteger;
import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.util.ClassUtils;
import org.springframework.util.NumberUtils;
import org.springframework.util.ObjectUtils;
/**
* Common supertype for operators that operate on either one or two operands.
* In the case of multiply or divide there would be two operands, but for
* unary plus or minus, there is only one.
*
* @author Andy Clement
* @author Juergen Hoeller
* @author Giovanni Dall'Oglio Risso
* @since 3.0
*/
public abstract class Operator extends SpelNodeImpl {
private final String operatorName;
// The descriptors of the runtime operand values are used if the discovered declared
// descriptors are not providing enough information (for example a generic type
// whose accessors seem to only be returning 'Object' - the actual descriptors may
// indicate 'int')
protected String leftActualDescriptor;
protected String rightActualDescriptor;
public Operator(String payload, int pos, SpelNodeImpl... operands) {
super(pos, operands);
this.operatorName = payload;
}
public SpelNodeImpl getLeftOperand() {
return this.children[0];
}
public SpelNodeImpl getRightOperand() {
return this.children[1];
}
public final String getOperatorName() {
return this.operatorName;
}
/**
* String format for all operators is the same '(' [operand] [operator] [operand] ')'
*/
@Override
public String toStringAST() {
StringBuilder sb = new StringBuilder("(");
sb.append(getChild(0).toStringAST());
for (int i = 1; i < getChildCount(); i++) {
sb.append(" ").append(getOperatorName()).append(" ");
sb.append(getChild(i).toStringAST());
}
sb.append(")");
return sb.toString();
}
protected boolean isCompilableOperatorUsingNumerics() {
SpelNodeImpl left = getLeftOperand();
SpelNodeImpl right= getRightOperand();
if (!left.isCompilable() || !right.isCompilable()) {
return false;
}
// Supported operand types for equals (at the moment)
String leftDesc = left.exitTypeDescriptor;
String rightDesc = right.exitTypeDescriptor;
DescriptorComparison dc = DescriptorComparison.checkNumericCompatibility(
leftDesc, rightDesc, this.leftActualDescriptor, this.rightActualDescriptor);
return (dc.areNumbers && dc.areCompatible);
}
/**
* Numeric comparison operators share very similar generated code, only differing in
* two comparison instructions.
*/
protected void generateComparisonCode(MethodVisitor mv, CodeFlow cf, int compInstruction1, int compInstruction2) {
String leftDesc = getLeftOperand().exitTypeDescriptor;
String rightDesc = getRightOperand().exitTypeDescriptor;
boolean unboxLeft = !CodeFlow.isPrimitive(leftDesc);
boolean unboxRight = !CodeFlow.isPrimitive(rightDesc);
DescriptorComparison dc = DescriptorComparison.checkNumericCompatibility(
leftDesc, rightDesc, this.leftActualDescriptor, this.rightActualDescriptor);
char targetType = dc.compatibleType; // CodeFlow.toPrimitiveTargetDesc(leftDesc);
cf.enterCompilationScope();
getLeftOperand().generateCode(mv, cf);
cf.exitCompilationScope();
if (unboxLeft) {
CodeFlow.insertUnboxInsns(mv, targetType, leftDesc);
}
cf.enterCompilationScope();
getRightOperand().generateCode(mv, cf);
cf.exitCompilationScope();
if (unboxRight) {
CodeFlow.insertUnboxInsns(mv, targetType, rightDesc);
}
// assert: SpelCompiler.boxingCompatible(leftDesc, rightDesc)
Label elseTarget = new Label();
Label endOfIf = new Label();
if (targetType == 'D') {
mv.visitInsn(DCMPG);
mv.visitJumpInsn(compInstruction1, elseTarget);
}
else if (targetType == 'F') {
mv.visitInsn(FCMPG);
mv.visitJumpInsn(compInstruction1, elseTarget);
}
else if (targetType == 'J') {
mv.visitInsn(LCMP);
mv.visitJumpInsn(compInstruction1, elseTarget);
}
else if (targetType == 'I') {
mv.visitJumpInsn(compInstruction2, elseTarget);
}
else {
throw new IllegalStateException("Unexpected descriptor " + leftDesc);
}
// Other numbers are not yet supported (isCompilable will not have returned true)
mv.visitInsn(ICONST_1);
mv.visitJumpInsn(GOTO,endOfIf);
mv.visitLabel(elseTarget);
mv.visitInsn(ICONST_0);
mv.visitLabel(endOfIf);
cf.pushDescriptor("Z");
}
/**
* Perform an equality check for the given operand values.
* <p>This method is not just used for reflective comparisons in subclasses
* but also from compiled expression code, which is why it needs to be
* declared as {@code public static} here.
* @param context the current evaluation context
* @param left the left-hand operand value
* @param right the right-hand operand value
*/
public static boolean equalityCheck(EvaluationContext context, Object left, Object right) {
if (left instanceof Number && right instanceof Number) {
Number leftNumber = (Number) left;
Number rightNumber = (Number) right;
if (leftNumber instanceof BigDecimal || rightNumber instanceof BigDecimal) {
BigDecimal leftBigDecimal = NumberUtils.convertNumberToTargetClass(leftNumber, BigDecimal.class);
BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class);
return (leftBigDecimal == null ? rightBigDecimal == null : leftBigDecimal.compareTo(rightBigDecimal) == 0);
}
else if (leftNumber instanceof Double || rightNumber instanceof Double) {
return (leftNumber.doubleValue() == rightNumber.doubleValue());
}
else if (leftNumber instanceof Float || rightNumber instanceof Float) {
return (leftNumber.floatValue() == rightNumber.floatValue());
}
else if (leftNumber instanceof BigInteger || rightNumber instanceof BigInteger) {
BigInteger leftBigInteger = NumberUtils.convertNumberToTargetClass(leftNumber, BigInteger.class);
BigInteger rightBigInteger = NumberUtils.convertNumberToTargetClass(rightNumber, BigInteger.class);
return (leftBigInteger == null ? rightBigInteger == null : leftBigInteger.compareTo(rightBigInteger) == 0);
}
else if (leftNumber instanceof Long || rightNumber instanceof Long) {
return (leftNumber.longValue() == rightNumber.longValue());
}
else if (leftNumber instanceof Integer || rightNumber instanceof Integer) {
return (leftNumber.intValue() == rightNumber.intValue());
}
else if (leftNumber instanceof Short || rightNumber instanceof Short) {
return (leftNumber.shortValue() == rightNumber.shortValue());
}
else if (leftNumber instanceof Byte || rightNumber instanceof Byte) {
return (leftNumber.byteValue() == rightNumber.byteValue());
}
else {
// Unknown Number subtypes -> best guess is double comparison
return (leftNumber.doubleValue() == rightNumber.doubleValue());
}
}
if (left instanceof CharSequence && right instanceof CharSequence) {
return left.toString().equals(right.toString());
}
if (ObjectUtils.nullSafeEquals(left, right)) {
return true;
}
if (left instanceof Comparable && right instanceof Comparable) {
Class<?> ancestor = ClassUtils.determineCommonAncestor(left.getClass(), right.getClass());
if (ancestor != null && Comparable.class.isAssignableFrom(ancestor)) {
return (context.getTypeComparator().compare(left, right) == 0);
}
}
return false;
}
/**
* A descriptor comparison encapsulates the result of comparing descriptor
* for two operands and describes at what level they are compatible.
*/
protected static class DescriptorComparison {
static DescriptorComparison NOT_NUMBERS = new DescriptorComparison(false, false, ' ');
static DescriptorComparison INCOMPATIBLE_NUMBERS = new DescriptorComparison(true, false, ' ');
final boolean areNumbers; // Were the two compared descriptor both for numbers?
final boolean areCompatible; // If they were numbers, were they compatible?
final char compatibleType; // When compatible, what is the descriptor of the common type
private DescriptorComparison(boolean areNumbers, boolean areCompatible, char compatibleType) {
this.areNumbers = areNumbers;
this.areCompatible = areCompatible;
this.compatibleType = compatibleType;
}
/**
* Return an object that indicates whether the input descriptors are compatible.
* <p>A declared descriptor is what could statically be determined (e.g. from looking
* at the return value of a property accessor method) whilst an actual descriptor
* is the type of an actual object that was returned, which may differ.
* <p>For generic types with unbound type variables, the declared descriptor
* discovered may be 'Object' but from the actual descriptor it is possible to
* observe that the objects are really numeric values (e.g. ints).
* @param leftDeclaredDescriptor the statically determinable left descriptor
* @param rightDeclaredDescriptor the statically determinable right descriptor
* @param leftActualDescriptor the dynamic/runtime left object descriptor
* @param rightActualDescriptor the dynamic/runtime right object descriptor
* @return a DescriptorComparison object indicating the type of compatibility, if any
*/
public static DescriptorComparison checkNumericCompatibility(String leftDeclaredDescriptor,
String rightDeclaredDescriptor, String leftActualDescriptor, String rightActualDescriptor) {
String ld = leftDeclaredDescriptor;
String rd = rightDeclaredDescriptor;
boolean leftNumeric = CodeFlow.isPrimitiveOrUnboxableSupportedNumberOrBoolean(ld);
boolean rightNumeric = CodeFlow.isPrimitiveOrUnboxableSupportedNumberOrBoolean(rd);
// If the declared descriptors aren't providing the information, try the actual descriptors
if (!leftNumeric && !ld.equals(leftActualDescriptor)) {
ld = leftActualDescriptor;
leftNumeric = CodeFlow.isPrimitiveOrUnboxableSupportedNumberOrBoolean(ld);
}
if (!rightNumeric && !rd.equals(rightActualDescriptor)) {
rd = rightActualDescriptor;
rightNumeric = CodeFlow.isPrimitiveOrUnboxableSupportedNumberOrBoolean(rd);
}
if (leftNumeric && rightNumeric) {
if (CodeFlow.areBoxingCompatible(ld, rd)) {
return new DescriptorComparison(true, true, CodeFlow.toPrimitiveTargetDesc(ld));
}
else {
return DescriptorComparison.INCOMPATIBLE_NUMBERS;
}
}
else {
return DescriptorComparison.NOT_NUMBERS;
}
}
}
}