diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Elvis.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Elvis.java index e61a8f5074a..64f1ec7ded3 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Elvis.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Elvis.java @@ -16,6 +16,7 @@ package org.springframework.expression.spel.ast; +import java.util.Objects; import java.util.Optional; import org.springframework.asm.Label; @@ -25,7 +26,6 @@ import org.springframework.expression.TypedValue; import org.springframework.expression.spel.CodeFlow; import org.springframework.expression.spel.ExpressionState; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; /** * Represents the Elvis operator {@code ?:}. @@ -60,6 +60,7 @@ public class Elvis extends SpelNodeImpl { */ @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { + TypedValue result; TypedValue leftHandTypedValue = this.children[0].getValueInternal(state); Object leftHandValue = leftHandTypedValue.getValue(); @@ -67,20 +68,21 @@ public class Elvis extends SpelNodeImpl { // Compilation is currently not supported for Optional with the Elvis operator. this.exitTypeDescriptor = null; if (optional.isPresent()) { - return new TypedValue(optional.get()); + result = new TypedValue(optional.get()); + } + else { + result = this.children[1].getValueInternal(state); } - return this.children[1].getValueInternal(state); } - - // If this check is changed, the generateCode method will need changing too - if (leftHandValue != null && !"".equals(leftHandValue)) { - return leftHandTypedValue; + // If this check is changed, the generateCode() method will need changing too + else if (leftHandValue != null && !"".equals(leftHandValue)) { + result = leftHandTypedValue; } else { - TypedValue result = this.children[1].getValueInternal(state); - computeExitTypeDescriptor(); - return result; + result = this.children[1].getValueInternal(state); } + computeExitTypeDescriptor(); + return result; } @Override @@ -92,22 +94,25 @@ public class Elvis extends SpelNodeImpl { public boolean isCompilable() { SpelNodeImpl condition = this.children[0]; SpelNodeImpl ifNullValue = this.children[1]; + String conditionDescriptor = condition.exitTypeDescriptor; + String ifNullValueDescriptor = ifNullValue.exitTypeDescriptor; + return (condition.isCompilable() && ifNullValue.isCompilable() && - condition.exitTypeDescriptor != null && ifNullValue.exitTypeDescriptor != null); + conditionDescriptor != null && ifNullValueDescriptor != null); } @Override public void generateCode(MethodVisitor mv, CodeFlow cf) { - // exit type descriptor can be null if both components are literal expressions - computeExitTypeDescriptor(); + Label elseTarget = new Label(); + Label endOfIf = new Label(); + cf.enterCompilationScope(); this.children[0].generateCode(mv, cf); String lastDesc = cf.lastDescriptor(); Assert.state(lastDesc != null, "No last descriptor"); CodeFlow.insertBoxIfNecessary(mv, lastDesc.charAt(0)); cf.exitCompilationScope(); - Label elseTarget = new Label(); - Label endOfIf = new Label(); + mv.visitInsn(DUP); mv.visitJumpInsn(IFNULL, elseTarget); // Also check if empty string, as per the code in the interpreted version @@ -116,6 +121,7 @@ public class Elvis extends SpelNodeImpl { mv.visitInsn(SWAP); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z",false); mv.visitJumpInsn(IFEQ, endOfIf); // if not empty, drop through to elseTarget + mv.visitLabel(elseTarget); mv.visitInsn(POP); cf.enterCompilationScope(); @@ -126,16 +132,20 @@ public class Elvis extends SpelNodeImpl { CodeFlow.insertBoxIfNecessary(mv, lastDesc.charAt(0)); } cf.exitCompilationScope(); + mv.visitLabel(endOfIf); + cf.pushDescriptor(this.exitTypeDescriptor); } private void computeExitTypeDescriptor() { - if (this.exitTypeDescriptor == null && this.children[0].exitTypeDescriptor != null && - this.children[1].exitTypeDescriptor != null) { - String conditionDescriptor = this.children[0].exitTypeDescriptor; - String ifNullValueDescriptor = this.children[1].exitTypeDescriptor; - if (ObjectUtils.nullSafeEquals(conditionDescriptor, ifNullValueDescriptor)) { + SpelNodeImpl condition = this.children[0]; + SpelNodeImpl ifNullValue = this.children[1]; + String conditionDescriptor = condition.exitTypeDescriptor; + String ifNullValueDescriptor = ifNullValue.exitTypeDescriptor; + + if (this.exitTypeDescriptor == null && conditionDescriptor != null && ifNullValueDescriptor != null) { + if (Objects.equals(conditionDescriptor, ifNullValueDescriptor)) { this.exitTypeDescriptor = conditionDescriptor; } else { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java index 91bb704fd66..096c55eb0ed 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java @@ -23,6 +23,7 @@ import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.StringJoiner; @@ -45,7 +46,6 @@ import org.springframework.expression.spel.SpelMessage; import org.springframework.expression.spel.support.ReflectiveMethodExecutor; import org.springframework.expression.spel.support.ReflectiveMethodResolver; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; /** * Expression language AST node that represents a method reference (i.e., a @@ -476,7 +476,7 @@ public class MethodReference extends SpelNodeImpl { public boolean isSuitable(Object target, @Nullable TypeDescriptor targetType, List argumentTypes) { return ((this.staticClass == null || this.staticClass == target) && - ObjectUtils.nullSafeEquals(this.targetType, targetType) && this.argumentTypes.equals(argumentTypes)); + Objects.equals(this.targetType, targetType) && this.argumentTypes.equals(argumentTypes)); } public boolean hasProxyTarget() { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Operator.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Operator.java index 336ed325044..56dfe050b79 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Operator.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Operator.java @@ -18,6 +18,7 @@ package org.springframework.expression.spel.ast; import java.math.BigDecimal; import java.math.BigInteger; +import java.util.Objects; import org.jspecify.annotations.Nullable; @@ -363,11 +364,11 @@ public abstract class Operator extends SpelNodeImpl { boolean rightNumeric = CodeFlow.isPrimitiveOrUnboxableSupportedNumberOrBoolean(rd); // If the declared descriptors aren't providing the information, try the actual descriptors - if (!leftNumeric && !ObjectUtils.nullSafeEquals(ld, leftActualDescriptor)) { + if (!leftNumeric && !Objects.equals(ld, leftActualDescriptor)) { ld = leftActualDescriptor; leftNumeric = CodeFlow.isPrimitiveOrUnboxableSupportedNumberOrBoolean(ld); } - if (!rightNumeric && !ObjectUtils.nullSafeEquals(rd, rightActualDescriptor)) { + if (!rightNumeric && !Objects.equals(rd, rightActualDescriptor)) { rd = rightActualDescriptor; rightNumeric = CodeFlow.isPrimitiveOrUnboxableSupportedNumberOrBoolean(rd); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/RealLiteral.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/RealLiteral.java index 3d211bd4455..02ac8c9f659 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/RealLiteral.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/RealLiteral.java @@ -54,5 +54,4 @@ public class RealLiteral extends Literal { cf.pushDescriptor(this.exitTypeDescriptor); } - } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Ternary.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Ternary.java index 062697ccd0b..c9ee2dfec7c 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Ternary.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Ternary.java @@ -16,6 +16,8 @@ package org.springframework.expression.spel.ast; +import java.util.Objects; + import org.springframework.asm.Label; import org.springframework.asm.MethodVisitor; import org.springframework.expression.EvaluationException; @@ -25,7 +27,6 @@ import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.expression.spel.SpelMessage; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; /** * Represents a ternary expression, for example: "someCheck()?true:false". @@ -66,35 +67,26 @@ public class Ternary extends SpelNodeImpl { return "(" + getChild(0).toStringAST() + " ? " + getChild(1).toStringAST() + " : " + getChild(2).toStringAST() + ")"; } - private void computeExitTypeDescriptor() { - if (this.exitTypeDescriptor == null && this.children[1].exitTypeDescriptor != null && - this.children[2].exitTypeDescriptor != null) { - String leftDescriptor = this.children[1].exitTypeDescriptor; - String rightDescriptor = this.children[2].exitTypeDescriptor; - if (ObjectUtils.nullSafeEquals(leftDescriptor, rightDescriptor)) { - this.exitTypeDescriptor = leftDescriptor; - } - else { - // Use the easiest to compute common supertype - this.exitTypeDescriptor = "Ljava/lang/Object"; - } - } - } - @Override public boolean isCompilable() { SpelNodeImpl condition = this.children[0]; SpelNodeImpl left = this.children[1]; SpelNodeImpl right = this.children[2]; + String conditionDescriptor = condition.exitTypeDescriptor; + String leftDescriptor = left.exitTypeDescriptor; + String rightDescriptor = right.exitTypeDescriptor; + return (condition.isCompilable() && left.isCompilable() && right.isCompilable() && - CodeFlow.isBooleanCompatible(condition.exitTypeDescriptor) && - left.exitTypeDescriptor != null && right.exitTypeDescriptor != null); + CodeFlow.isBooleanCompatible(conditionDescriptor) && + leftDescriptor != null && rightDescriptor != null); } @Override public void generateCode(MethodVisitor mv, CodeFlow cf) { - // May reach here without it computed if all elements are literals + // May get here without the exit descriptor having been computed, if + // all elements are literals. computeExitTypeDescriptor(); + cf.enterCompilationScope(); this.children[0].generateCode(mv, cf); String lastDesc = cf.lastDescriptor(); @@ -128,4 +120,18 @@ public class Ternary extends SpelNodeImpl { cf.pushDescriptor(this.exitTypeDescriptor); } + private void computeExitTypeDescriptor() { + String leftDescriptor = this.children[1].exitTypeDescriptor; + String rightDescriptor = this.children[2].exitTypeDescriptor; + if (this.exitTypeDescriptor == null && leftDescriptor != null && rightDescriptor != null) { + if (Objects.equals(leftDescriptor, rightDescriptor)) { + this.exitTypeDescriptor = leftDescriptor; + } + else { + // Use the easiest to compute common supertype + this.exitTypeDescriptor = "Ljava/lang/Object"; + } + } + } + }