Support compilation of the SpEL operator OpModulus

This commit enables the modulus operator to be compiled when
it is used as part of a SpEL expression.

Issue: SPR-12041
This commit is contained in:
Andy Clement 2014-07-29 12:34:44 -07:00
parent 59080ff2b2
commit d30174897d
2 changed files with 134 additions and 1 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@ -18,9 +18,11 @@ package org.springframework.expression.spel.ast;
import java.math.BigDecimal;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.util.NumberUtils;
@ -54,21 +56,81 @@ public class OpModulus extends Operator {
}
if (leftNumber instanceof Double || rightNumber instanceof Double) {
if (leftNumber instanceof Double && rightNumber instanceof Double) {
this.exitTypeDescriptor = "D";
}
return new TypedValue(leftNumber.doubleValue() % rightNumber.doubleValue());
}
if (leftNumber instanceof Float || rightNumber instanceof Float) {
if (leftNumber instanceof Float && rightNumber instanceof Float) {
this.exitTypeDescriptor = "F";
}
return new TypedValue(leftNumber.floatValue() % rightNumber.floatValue());
}
if (leftNumber instanceof Long || rightNumber instanceof Long) {
if (leftNumber instanceof Long && rightNumber instanceof Long) {
this.exitTypeDescriptor = "J";
}
return new TypedValue(leftNumber.longValue() % rightNumber.longValue());
}
if (leftNumber instanceof Integer && rightNumber instanceof Integer) {
this.exitTypeDescriptor = "I";
}
return new TypedValue(leftNumber.intValue() % rightNumber.intValue());
}
return state.operate(Operation.MODULUS, leftOperand, rightOperand);
}
@Override
public boolean isCompilable() {
if (!getLeftOperand().isCompilable()) {
return false;
}
if (this.children.length>1) {
if (!getRightOperand().isCompilable()) {
return false;
}
}
return this.exitTypeDescriptor!=null;
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
getLeftOperand().generateCode(mv, codeflow);
String leftdesc = getLeftOperand().getExitDescriptor();
if (!CodeFlow.isPrimitive(leftdesc)) {
CodeFlow.insertUnboxInsns(mv, this.exitTypeDescriptor.charAt(0), leftdesc);
}
if (this.children.length > 1) {
codeflow.enterCompilationScope();
getRightOperand().generateCode(mv, codeflow);
String rightdesc = getRightOperand().getExitDescriptor();
codeflow.exitCompilationScope();
if (!CodeFlow.isPrimitive(rightdesc)) {
CodeFlow.insertUnboxInsns(mv, this.exitTypeDescriptor.charAt(0), rightdesc);
}
switch (this.exitTypeDescriptor.charAt(0)) {
case 'I':
mv.visitInsn(IREM);
break;
case 'J':
mv.visitInsn(LREM);
break;
case 'F':
mv.visitInsn(FREM);
break;
case 'D':
mv.visitInsn(DREM);
break;
default:
throw new IllegalStateException("Unrecognized exit descriptor: '"+this.exitTypeDescriptor+"'");
}
}
codeflow.pushDescriptor(this.exitTypeDescriptor);
}
}

View File

@ -1419,6 +1419,77 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertEquals(-2.0f,expression.getValue());
}
@Test
public void opModulus_12041() throws Exception {
expression = parse("2%2");
assertEquals(0,expression.getValue());
assertCanCompile(expression);
assertEquals(0,expression.getValue());
expression = parse("payload%2==0");
assertTrue(expression.getValue(new GenericMessageTestHelper<Integer>(4),Boolean.TYPE));
assertFalse(expression.getValue(new GenericMessageTestHelper<Integer>(5),Boolean.TYPE));
assertCanCompile(expression);
assertTrue(expression.getValue(new GenericMessageTestHelper<Integer>(4),Boolean.TYPE));
assertFalse(expression.getValue(new GenericMessageTestHelper<Integer>(5),Boolean.TYPE));
expression = parse("8%3");
assertEquals(2,expression.getValue());
assertCanCompile(expression);
assertEquals(2,expression.getValue());
expression = parse("17L%5L");
assertEquals(2L,expression.getValue());
assertCanCompile(expression);
assertEquals(2L,expression.getValue());
expression = parse("3.0f%2.0f");
assertEquals(1.0f,expression.getValue());
assertCanCompile(expression);
assertEquals(1.0f,expression.getValue());
expression = parse("3.0d%4.0d");
assertEquals(3.0d,expression.getValue());
assertCanCompile(expression);
assertEquals(3.0d,expression.getValue());
expression = parse("T(Float).valueOf(6.0f)%2");
assertEquals(0.0f,expression.getValue());
assertCantCompile(expression);
expression = parse("T(Float).valueOf(6.0f)%4");
assertEquals(2.0f,expression.getValue());
assertCantCompile(expression);
expression = parse("T(Float).valueOf(8.0f)%T(Float).valueOf(3.0f)");
assertEquals(2.0f,expression.getValue());
assertCanCompile(expression);
assertEquals(2.0f,expression.getValue());
expression = parse("13L%T(Long).valueOf(4L)");
assertEquals(1L,expression.getValue());
assertCanCompile(expression);
assertEquals(1L,expression.getValue());
expression = parse("T(Long).valueOf(44L)%12");
assertEquals(8L,expression.getValue());
assertCantCompile(expression);
expression = parse("T(Long).valueOf(9L)%T(Long).valueOf(2L)");
assertEquals(1L,expression.getValue());
assertCanCompile(expression);
assertEquals(1L,expression.getValue());
expression = parse("7L%T(Long).valueOf(2L)");
assertEquals(1L,expression.getValue());
assertCanCompile(expression);
assertEquals(1L,expression.getValue());
expression = parse("T(Float).valueOf(9.0f)%-T(Float).valueOf(4.0f)");
assertEquals(1.0f,expression.getValue());
assertCanCompile(expression);
assertEquals(1.0f,expression.getValue());
}
@Test
public void constructorReference() throws Exception {