@ -17,8 +17,11 @@
@@ -17,8 +17,11 @@
package org.springframework.expression.spel ;
import java.util.Collection ;
import java.util.List ;
import java.util.Set ;
import org.assertj.core.api.ThrowableTypeAssert ;
import org.junit.jupiter.api.Disabled ;
import org.junit.jupiter.api.Test ;
import org.springframework.expression.EvaluationException ;
@ -35,6 +38,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -35,6 +38,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*
* @author Keith Donald
* @author Andy Clement
* @author Sam Brannen
* /
class SetValueTests extends AbstractExpressionTests {
@ -42,178 +46,200 @@ class SetValueTests extends AbstractExpressionTests {
@@ -42,178 +46,200 @@ class SetValueTests extends AbstractExpressionTests {
@Test
void testSetProperty ( ) {
setValue ( "wonNobelPrize" , true ) ;
void setValueFailsWhenLeftOperandIsNotAssignable ( ) {
setValueAndExpectError ( "3=4" , "enigma" ) ;
}
@Test
void te stS etNested Property( ) {
setValue ( "placeOfBirth.city" , "Wien" ) ;
void setProperty ( ) {
setValue ( "wonNobelPrize" , true ) ;
}
@Test
void testSetArrayElementValue ( ) {
setValue ( "inventions[0]" , "Just the telephone " ) ;
void setNestedProperty ( ) {
setValue ( "placeOfBirth.city" , "Wien " ) ;
}
@Test
void testErrorCase ( ) {
setValueExpectError ( "3=4" , null ) ;
void setArrayElementValueToStringFromString ( ) {
setValue ( "inventions[0]" , "Just the telephone" ) ;
}
@Test
void testSetElementOfNull ( ) {
setValueExpectError ( "new org.springframework.expression.spel.testresources.Inventor().inventions[1]" ,
SpelMessage . CANNOT_INDEX_INTO_NULL_VALUE ) ;
void setArrayElementInNonexistentIndex ( ) {
setValueAnd ExpectError (
"new org.springframework.expression.spel.testresources.Inventor().inventions[1]" , "my invention" ) ;
}
@Test
void testSetArrayElementValueAllPrimitiveTypes ( ) {
void setArrayElementValueToPrimitiveTypeFromWrapperType ( ) {
// All primitive values below are auto-boxed into their wrapper types.
setValue ( "arrayContainer.booleans[1]" , false ) ;
setValue ( "arrayContainer.chars[1]" , ( char ) 3 ) ;
setValue ( "arrayContainer.shorts[1]" , ( short ) 3 ) ;
setValue ( "arrayContainer.bytes[1]" , ( byte ) 3 ) ;
setValue ( "arrayContainer.ints[1]" , 3 ) ;
setValue ( "arrayContainer.longs[1]" , 3L ) ;
setValue ( "arrayContainer.floats[1]" , 3 . 0f ) ;
setValue ( "arrayContainer.booleans[1]" , false ) ;
setValue ( "arrayContainer.doubles[1]" , 3 . 4d ) ;
setValue ( "arrayContainer.shorts[1]" , ( short ) 3 ) ;
setValue ( "arrayContainer.longs[1]" , 3L ) ;
setValue ( "arrayContainer.bytes[1]" , ( byte ) 3 ) ;
setValue ( "arrayContainer.chars[1]" , ( char ) 3 ) ;
}
@Test
void testIsWritableForInvalidExpressions_SPR10610 ( ) {
StandardEvaluationContext lContext = TestScenarioCreator . getTestEvaluationContext ( ) ;
void setArrayElementValueToPrimitiveTypeFromStringResultsInError ( ) {
String notPrimitiveOrWrapper = "not primitive or wrapper" ;
setValueAndExpectError ( "arrayContainer.booleans[1]" , notPrimitiveOrWrapper ) ;
setValueAndExpectError ( "arrayContainer.chars[1]" , notPrimitiveOrWrapper ) ;
setValueAndExpectError ( "arrayContainer.shorts[1]" , notPrimitiveOrWrapper ) ;
setValueAndExpectError ( "arrayContainer.bytes[1]" , notPrimitiveOrWrapper ) ;
setValueAndExpectError ( "arrayContainer.ints[1]" , notPrimitiveOrWrapper ) ;
setValueAndExpectError ( "arrayContainer.longs[1]" , notPrimitiveOrWrapper ) ;
setValueAndExpectError ( "arrayContainer.floats[1]" , notPrimitiveOrWrapper ) ;
setValueAndExpectError ( "arrayContainer.doubles[1]" , notPrimitiveOrWrapper ) ;
}
@Test
void setArrayElementValueToPrimitiveTypeFromSingleElementList ( ) {
setValue ( "arrayContainer.booleans[1]" , List . of ( false ) , false ) ;
setValue ( "arrayContainer.chars[1]" , List . of ( 'a' ) , 'a' ) ;
setValue ( "arrayContainer.shorts[1]" , List . of ( ( short ) 3 ) , ( short ) 3 ) ;
setValue ( "arrayContainer.bytes[1]" , List . of ( ( byte ) 3 ) , ( byte ) 3 ) ;
setValue ( "arrayContainer.ints[1]" , List . of ( 42 ) , 42 ) ;
setValue ( "arrayContainer.longs[1]" , List . of ( 42L ) , 42L ) ;
setValue ( "arrayContainer.floats[1]" , List . of ( 42F ) , 42F ) ;
setValue ( "arrayContainer.doubles[1]" , List . of ( 42D ) , 42D ) ;
}
@Disabled ( "Disabled due to bug in Indexer.setArrayElement() regarding primitive/wrapper types" )
@Test
void setArrayElementValueToPrimitiveTypeFromEmptyListResultsInError ( ) {
List < Object > emptyList = List . of ( ) ;
// TODO These fail because CollectionToObjectConverter returns null.
// It currently throws: java.lang.IllegalStateException: Null conversion result for index [[]].
// Whereas, it should throw a SpelEvaluationException.
setValueAndExpectError ( "arrayContainer.booleans[1]" , emptyList ) ;
setValueAndExpectError ( "arrayContainer.chars[1]" , emptyList ) ;
setValueAndExpectError ( "arrayContainer.shorts[1]" , emptyList ) ;
setValueAndExpectError ( "arrayContainer.bytes[1]" , emptyList ) ;
setValueAndExpectError ( "arrayContainer.ints[1]" , emptyList ) ;
setValueAndExpectError ( "arrayContainer.longs[1]" , emptyList ) ;
setValueAndExpectError ( "arrayContainer.floats[1]" , emptyList ) ;
setValueAndExpectError ( "arrayContainer.doubles[1]" , emptyList ) ;
}
@Test // gh-15239
void isWritableForInvalidExpressions ( ) {
// Do NOT reuse super.context!
StandardEvaluationContext localContext = TestScenarioCreator . getTestEvaluationContext ( ) ;
// PROPERTYORFIELDREFERENCE
// Non-existent field (or property):
Expression e1 = parser . parseExpression ( "arrayContainer.wibble" ) ;
assertThat ( e1 . isWritable ( lContext ) ) . as ( "Should not be writable!" ) . isFalse ( ) ;
assertThat ( e1 . isWritable ( local Context ) ) . as ( "Should not be writable!" ) . isFalse ( ) ;
Expression e2 = parser . parseExpression ( "arrayContainer.wibble.foo" ) ;
assertThatExceptionOfType ( SpelEvaluationException . class ) . isThrownBy ( ( ) - >
e2 . isWritable ( lContext ) ) ;
// org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 15): Property or field 'wibble' cannot be found on object of type 'org.springframework.expression.spel.testresources.ArrayContainer' - maybe not public?
// at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:225)
assertThatSpelEvaluationException ( ) . isThrownBy ( ( ) - > e2 . isWritable ( localContext ) ) ;
// VARIABLE
// the variable does not exist (but that is OK, we should be writable)
Expression e3 = parser . parseExpression ( "#madeup1" ) ;
assertThat ( e3 . isWritable ( lContext ) ) . as ( "Should be writable!" ) . isTrue ( ) ;
assertThat ( e3 . isWritable ( local Context ) ) . as ( "Should be writable!" ) . isTrue ( ) ;
Expression e4 = parser . parseExpression ( "#madeup2.bar" ) ; // compound expression
assertThat ( e4 . isWritable ( lContext ) ) . as ( "Should not be writable!" ) . isFalse ( ) ;
assertThat ( e4 . isWritable ( local Context ) ) . as ( "Should not be writable!" ) . isFalse ( ) ;
// INDEXER
// non-existent indexer (wibble made up)
Expression e5 = parser . parseExpression ( "arrayContainer.wibble[99]" ) ;
assertThatExceptionOfType ( SpelEvaluationException . class ) . isThrownBy ( ( ) - >
e5 . isWritable ( lContext ) ) ;
assertThatSpelEvaluationException ( ) . isThrownBy ( ( ) - > e5 . isWritable ( localContext ) ) ;
// non-existent indexer (index via a string)
Expression e6 = parser . parseExpression ( "arrayContainer.ints['abc']" ) ;
assertThatExceptionOfType ( SpelEvaluationException . class ) . isThrownBy ( ( ) - >
e6 . isWritable ( lContext ) ) ;
assertThatSpelEvaluationException ( ) . isThrownBy ( ( ) - > e6 . isWritable ( localContext ) ) ;
}
@Test
void testSetArrayElementValueAllPrimitiveTypesErrors ( ) {
// none of these sets are possible due to (expected) conversion problems
setValueExpectError ( "arrayContainer.ints[1]" , "wibble" ) ;
setValueExpectError ( "arrayContainer.floats[1]" , "dribble" ) ;
setValueExpectError ( "arrayContainer.booleans[1]" , "nein" ) ;
// TODO -- this fails with NPE due to ArrayToObject converter - discuss with Andy
//setValueExpectError("arrayContainer.doubles[1]", new ArrayList<String>());
//setValueExpectError("arrayContainer.shorts[1]", new ArrayList<String>());
//setValueExpectError("arrayContainer.longs[1]", new ArrayList<String>());
setValueExpectError ( "arrayContainer.bytes[1]" , "NaB" ) ;
setValueExpectError ( "arrayContainer.chars[1]" , "NaC" ) ;
}
@Test
void testSetArrayElementNestedValue ( ) {
void setArrayElementNestedValue ( ) {
setValue ( "placesLived[0].city" , "Wien" ) ;
}
@Test
void te stS etListElementValue( ) {
void setListElementValue ( ) {
setValue ( "placesLivedList[0]" , new PlaceOfBirth ( "Wien" ) ) ;
}
@Test
void testSetGenericListElementValueTypeCoercion ( ) {
// TODO currently failing since setValue does a getValue and "Wien" string != PlaceOfBirth - check with andy
void setGenericListElementValueTypeCoercion ( ) {
setValue ( "placesLivedList[0]" , "Wien" ) ;
}
@Test
void te stS etGenericListElementValueTypeCoercionOK( ) {
void setGenericListElementValueTypeCoercionOK ( ) {
setValue ( "booleanList[0]" , "true" , Boolean . TRUE ) ;
}
@Test
void te stS etListElementNestedValue( ) {
void setListElementNestedValue ( ) {
setValue ( "placesLived[0].city" , "Wien" ) ;
}
@Test
void te stS etArrayElementInvalidIndex( ) {
setValueExpectError ( "placesLived[23]" , "Wien" ) ;
setValueExpectError ( "placesLivedList[23]" , "Wien" ) ;
void setArrayElementInvalidIndex ( ) {
setValueAnd ExpectError ( "placesLived[23]" , "Wien" ) ;
setValueAnd ExpectError ( "placesLivedList[23]" , "Wien" ) ;
}
@Test
void te stS etMapElements( ) {
void setMapElements ( ) {
setValue ( "testMap['montag']" , "lundi" ) ;
}
@Test
void testI ndexingIntoUnsupportedType( ) {
setValueExpectError ( "'hello'[3]" , 'p' ) ;
void i ndexingIntoUnsupportedType( ) {
setValueAnd ExpectError ( "'hello'[3]" , 'p' ) ;
}
@Test
void te stS etPropertyTypeCoercion( ) {
void setPropertyTypeCoercion ( ) {
setValue ( "publicBoolean" , "true" , Boolean . TRUE ) ;
}
@Test
void te stS etPropertyTypeCoercionThroughSetter( ) {
void setPropertyTypeCoercionThroughSetter ( ) {
setValue ( "SomeProperty" , "true" , Boolean . TRUE ) ;
}
@Test
void testAssign ( ) {
StandardEvaluationContext eContext = TestScenarioCreator . getTestEvaluationContext ( ) ;
void assign ( ) {
// Do NOT reuse super.context!
StandardEvaluationContext localContext = TestScenarioCreator . getTestEvaluationContext ( ) ;
Expression e = parse ( "publicName='Andy'" ) ;
assertThat ( e . isWritable ( e Context) ) . isFalse ( ) ;
assertThat ( e . getValue ( e Context) ) . isEqualTo ( "Andy" ) ;
assertThat ( e . isWritable ( local Context) ) . isFalse ( ) ;
assertThat ( e . getValue ( local Context) ) . isEqualTo ( "Andy" ) ;
}
/ *
* Testing the coercion of both the keys and the values to the correct type
/ * *
* Testing the coercion of both the keys and the values to the correct type .
* /
@Test
void testSetGenericMapElementRequiresCoercion ( ) {
StandardEvaluationContext eContext = TestScenarioCreator . getTestEvaluationContext ( ) ;
void setGenericMapElementRequiresCoercion ( ) {
// Do NOT reuse super.context!
StandardEvaluationContext localContext = TestScenarioCreator . getTestEvaluationContext ( ) ;
Expression e = parse ( "mapOfStringToBoolean[42]" ) ;
assertThat ( e . getValue ( e Context) ) . isNull ( ) ;
assertThat ( e . getValue ( local Context) ) . isNull ( ) ;
// Key should be coerced to string representation of 42
e . setValue ( e Context, "true" ) ;
e . setValue ( local Context, "true" ) ;
// All keys should be strings
Set < ? > ks = parse ( "mapOfStringToBoolean.keySet()" ) . getValue ( eContext , Set . class ) ;
for ( Object o : ks ) {
assertThat ( o . getClass ( ) ) . isEqualTo ( String . class ) ;
}
Set < ? > keys = parse ( "mapOfStringToBoolean.keySet()" ) . getValue ( localContext , Set . class ) ;
assertThat ( keys ) . allSatisfy ( key - > assertThat ( key ) . isExactlyInstanceOf ( String . class ) ) ;
// All values should be booleans
Collection < ? > vs = parse ( "mapOfStringToBoolean.values()" ) . getValue ( eContext , Collection . class ) ;
for ( Object o : vs ) {
assertThat ( o . getClass ( ) ) . isEqualTo ( Boolean . class ) ;
}
Collection < ? > values = parse ( "mapOfStringToBoolean.values()" ) . getValue ( localContext , Collection . class ) ;
assertThat ( values ) . allSatisfy ( key - > assertThat ( key ) . isExactlyInstanceOf ( Boolean . class ) ) ;
// One final test check coercion on the key for a map lookup
Object o = e . getValue ( e Context) ;
Object o = e . getValue ( local Context) ;
assertThat ( o ) . isEqualTo ( Boolean . TRUE ) ;
}
@ -225,28 +251,30 @@ class SetValueTests extends AbstractExpressionTests {
@@ -225,28 +251,30 @@ class SetValueTests extends AbstractExpressionTests {
/ * *
* Call setValue ( ) but expect it to fail .
* /
protected void setValueExpectError ( String expression , Object value ) {
private void setValueAnd ExpectError ( String expression , Object value ) {
Expression e = parser . parseExpression ( expression ) ;
assertThat ( e ) . isNotNull ( ) ;
if ( DEBUG ) {
SpelUtilities . printAbstractSyntaxTree ( System . out , e ) ;
}
StandardEvaluationContext lContext = TestScenarioCreator . getTestEvaluationContext ( ) ;
assertThatExceptionOfType ( EvaluationException . class ) . isThrownBy ( ( ) - >
e . setValue ( lContext , value ) ) ;
// Do NOT reuse super.context!
StandardEvaluationContext localContext = TestScenarioCreator . getTestEvaluationContext ( ) ;
assertThatSpelEvaluationException ( ) . isThrownBy ( ( ) - > e . setValue ( loca lContext, value ) ) ;
}
protected void setValue ( String expression , Object value ) {
private void setValue ( String expression , Object value ) {
Class < ? > expectedType = value . getClass ( ) ;
try {
Expression e = parser . parseExpression ( expression ) ;
assertThat ( e ) . isNotNull ( ) ;
if ( DEBUG ) {
SpelUtilities . printAbstractSyntaxTree ( System . out , e ) ;
}
StandardEvaluationContext lContext = TestScenarioCreator . getTestEvaluationContext ( ) ;
assertThat ( e . isWritable ( lContext ) ) . as ( "Expression is not writeable but should be" ) . isTrue ( ) ;
e . setValue ( lContext , value ) ;
assertThat ( e . getValue ( lContext , value . getClass ( ) ) ) . as ( "Retrieved value was not equal to set value" ) . isEqualTo ( value ) ;
// Do NOT reuse super.context!
StandardEvaluationContext localContext = TestScenarioCreator . getTestEvaluationContext ( ) ;
assertThat ( e . isWritable ( localContext ) ) . as ( "Expression is not writeable but should be" ) . isTrue ( ) ;
e . setValue ( localContext , value ) ;
assertThat ( e . getValue ( localContext , expectedType ) ) . as ( "Retrieved value was not equal to set value" ) . isEqualTo ( value ) ;
}
catch ( EvaluationException | ParseException ex ) {
throw new AssertionError ( "Unexpected Exception: " + ex . getMessage ( ) , ex ) ;
@ -254,26 +282,29 @@ class SetValueTests extends AbstractExpressionTests {
@@ -254,26 +282,29 @@ class SetValueTests extends AbstractExpressionTests {
}
/ * *
* For use when coercion is happening during a setValue ( ) . The expectedValue should be
* For use when coercion is happening during setValue ( ) . The expectedValue should be
* the coerced form of the value .
* /
protected void setValue ( String expression , Object value , Object expectedValue ) {
private void setValue ( String expression , Object value , Object expectedValue ) {
try {
Expression e = parser . parseExpression ( expression ) ;
assertThat ( e ) . isNotNull ( ) ;
if ( DEBUG ) {
SpelUtilities . printAbstractSyntaxTree ( System . out , e ) ;
}
StandardEvaluationContext lContext = TestScenarioCreator . getTestEvaluationContext ( ) ;
assertThat ( e . isWritable ( lContext ) ) . as ( "Expression is not writeable but should be" ) . isTrue ( ) ;
e . setValue ( lContext , value ) ;
Object a = expectedValue ;
Object b = e . getValue ( lContext ) ;
assertThat ( a ) . isEqualTo ( b ) ;
// Do NOT reuse super.context!
StandardEvaluationContext localContext = TestScenarioCreator . getTestEvaluationContext ( ) ;
assertThat ( e . isWritable ( localContext ) ) . as ( "Expression is not writeable but should be" ) . isTrue ( ) ;
e . setValue ( localContext , value ) ;
assertThat ( expectedValue ) . isEqualTo ( e . getValue ( localContext ) ) ;
}
catch ( EvaluationException | ParseException ex ) {
throw new AssertionError ( "Unexpected Exception: " + ex . getMessage ( ) , ex ) ;
}
}
private static ThrowableTypeAssert < SpelEvaluationException > assertThatSpelEvaluationException ( ) {
return assertThatExceptionOfType ( SpelEvaluationException . class ) ;
}
}