Support case-insensitive null literals in SpEL

Prior to this commit null literals in SpEL expressions had to be
specified as "null" (i.e., all lowercase).

With this commit null literals in SpEL expressions are interpreted in a
case-insensitive manner, analogous to the current support for boolean
literals.

Issue: SPR-9613
master
Andy Clement 12 years ago committed by Sam Brannen
parent 529e62921d
commit a7418f480c
  1. 2
      spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java
  2. 251
      spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java
  3. 1
      src/dist/changelog.txt

@ -513,7 +513,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
private boolean maybeEatNullReference() {
if (peekToken(TokenKind.IDENTIFIER)) {
Token nullToken = peekToken();
if (!nullToken.stringValue().equals("null")) {
if (!nullToken.stringValue().toLowerCase().equals("null")) {
return false;
}
nextToken();

@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2012 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.
@ -16,12 +16,13 @@
package org.springframework.expression.spel;
import static org.junit.Assert.*;
import java.util.List;
import java.util.Map;
import junit.framework.Assert;
import org.junit.Test;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
@ -38,6 +39,7 @@ import org.springframework.expression.spel.testresources.TestPerson;
*
* @author Andy Clement
* @author Mark Fisher
* @author Sam Brannen
* @since 3.0
*/
public class EvaluationTests extends ExpressionTestCase {
@ -49,76 +51,66 @@ public class EvaluationTests extends ExpressionTestCase {
TestClass testClass = new TestClass();
Object o = null;
o = expression.getValue(new StandardEvaluationContext(testClass));
Assert.assertEquals("",o);
assertEquals("", o);
o = parser.parseExpression("list[3]").getValue(new StandardEvaluationContext(testClass));
Assert.assertEquals("",o);
Assert.assertEquals(4, testClass.list.size());
assertEquals("", o);
assertEquals(4, testClass.list.size());
try {
o = parser.parseExpression("list2[3]").getValue(new StandardEvaluationContext(testClass));
Assert.fail();
fail();
} catch (EvaluationException ee) {
ee.printStackTrace();
// success!
}
o = parser.parseExpression("foo[3]").getValue(new StandardEvaluationContext(testClass));
Assert.assertEquals("",o);
Assert.assertEquals(4, testClass.getFoo().size());
assertEquals("", o);
assertEquals(4, testClass.getFoo().size());
}
@Test
public void testCreateMapsOnAttemptToIndexNull01() throws EvaluationException, ParseException {
@Test(expected = SpelEvaluationException.class)
public void testCreateMapsOnAttemptToIndexNull01() throws Exception {
TestClass testClass = new TestClass();
StandardEvaluationContext ctx = new StandardEvaluationContext(testClass);
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
Object o = null;
o = parser.parseExpression("map['a']").getValue(ctx);
Assert.assertNull(o);
assertNull(o);
o = parser.parseExpression("map").getValue(ctx);
Assert.assertNotNull(o);
try {
o = parser.parseExpression("map2['a']").getValue(ctx);
// fail!
Assert.fail("map2 should be null, there is no setter");
} catch (Exception e) {
// success!
}
assertNotNull(o);
o = parser.parseExpression("map2['a']").getValue(ctx);
// map2 should be null, there is no setter
}
@Test
public void testCreateObjectsOnAttemptToReferenceNull() throws EvaluationException, ParseException {
// wibble2 should be null (cannot be initialized dynamically), there is no setter
@Test(expected = SpelEvaluationException.class)
public void testCreateObjectsOnAttemptToReferenceNull() throws Exception {
TestClass testClass = new TestClass();
StandardEvaluationContext ctx = new StandardEvaluationContext(testClass);
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
Object o = null;
o = parser.parseExpression("wibble.bar").getValue(ctx);
Assert.assertEquals("hello",o);
assertEquals("hello", o);
o = parser.parseExpression("wibble").getValue(ctx);
Assert.assertNotNull(o);
try {
o = parser.parseExpression("wibble2.bar").getValue(ctx);
// fail!
Assert.fail("wibble2 should be null (cannot be initialized dynamically), there is no setter");
} catch (Exception e) {
// success!
}
assertNotNull(o);
o = parser.parseExpression("wibble2.bar").getValue(ctx);
}
static class TestClass {
public Foo wibble;
private Foo wibble2;
public Map map;
public Map<String,Integer> mapStringToInteger;
public Map<String, Integer> mapStringToInteger;
public List<String> list;
public List list2;
private Map map2;
private List<String> foo;
public Map getMap2() { return this.map2; }
public Foo getWibble2() { return this.wibble2; }
// public void setMap2(Map m) { this.map2 = m; }
private List<String> foo;
public List<String> getFoo() { return this.foo; }
public void setFoo(List<String> newfoo) { this.foo = newfoo; }
}
@ -127,18 +119,19 @@ public class EvaluationTests extends ExpressionTestCase {
public Foo() {}
public String bar = "hello";
}
@Test
public void testElvis01() {
evaluate("'Andy'?:'Dave'","Andy",String.class);
evaluate("null?:'Dave'","Dave",String.class);
evaluate("'Andy'?:'Dave'", "Andy", String.class);
evaluate("null?:'Dave'", "Dave", String.class);
}
@Test
public void testSafeNavigation() {
evaluate("null?.null?.null",null,null);
evaluate("null?.null?.null", null, null);
}
@Test
public void testRelOperatorGT01() {
evaluate("3 > 6", "false", Boolean.class);
@ -163,7 +156,7 @@ public class EvaluationTests extends ExpressionTestCase {
public void testRelOperatorGE02() {
evaluate("3 >= 3", "true", Boolean.class);
}
@Test
public void testRelOperatorsInstanceof01() {
evaluate("'xyz' instanceof T(int)", "false", Boolean.class);
@ -218,12 +211,12 @@ public class EvaluationTests extends ExpressionTestCase {
// property access
@Test
public void testPropertyField01() {
evaluate("name", "Nikola Tesla", String.class, false);
evaluate("name", "Nikola Tesla", String.class, false);
// not writable because (1) name is private (2) there is no setter, only a getter
evaluateAndCheckError("madeup", SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE, 0, "madeup",
"org.springframework.expression.spel.testresources.Inventor");
"org.springframework.expression.spel.testresources.Inventor");
}
@Test
public void testPropertyField02_SPR7100() {
evaluate("_name", "Nikola Tesla", String.class);
@ -234,15 +227,14 @@ public class EvaluationTests extends ExpressionTestCase {
public void testRogueTrailingDotCausesNPE_SPR6866() {
try {
new SpelExpressionParser().parseExpression("placeOfBirth.foo.");
Assert.fail("Should have failed to parse");
fail("Should have failed to parse");
} catch (ParseException e) {
Assert.assertTrue(e instanceof SpelParseException);
SpelParseException spe = (SpelParseException)e;
Assert.assertEquals(SpelMessage.OOD,spe.getMessageCode());
Assert.assertEquals(16,spe.getPosition());
}
assertTrue(e instanceof SpelParseException);
SpelParseException spe = (SpelParseException) e;
assertEquals(SpelMessage.OOD, spe.getMessageCode());
assertEquals(16, spe.getPosition());
}
}
// nested properties
@Test
@ -259,13 +251,13 @@ public class EvaluationTests extends ExpressionTestCase {
public void testPropertiesNested03() throws ParseException {
try {
new SpelExpressionParser().parseRaw("placeOfBirth.23");
Assert.fail();
fail();
} catch (SpelParseException spe) {
Assert.assertEquals(spe.getMessageCode(), SpelMessage.UNEXPECTED_DATA_AFTER_DOT);
Assert.assertEquals("23", spe.getInserts()[0]);
assertEquals(spe.getMessageCode(), SpelMessage.UNEXPECTED_DATA_AFTER_DOT);
assertEquals("23", spe.getInserts()[0]);
}
}
// methods
@Test
public void testMethods01() {
@ -287,21 +279,21 @@ public class EvaluationTests extends ExpressionTestCase {
public void testConstructorInvocation05() {
evaluate("new java.lang.String('foobar')", "foobar", String.class);
}
@Test
public void testConstructorInvocation06() throws Exception {
// repeated evaluation to drive use of cached executor
SpelExpression expr = (SpelExpression)parser.parseExpression("new String('wibble')");
SpelExpression expr = (SpelExpression) parser.parseExpression("new String('wibble')");
String newString = expr.getValue(String.class);
Assert.assertEquals("wibble",newString);
assertEquals("wibble", newString);
newString = expr.getValue(String.class);
Assert.assertEquals("wibble",newString);
assertEquals("wibble", newString);
// not writable
Assert.assertFalse(expr.isWritable(new StandardEvaluationContext()));
assertFalse(expr.isWritable(new StandardEvaluationContext()));
// ast
Assert.assertEquals("new String('wibble')",expr.toStringAST());
assertEquals("new String('wibble')", expr.toStringAST());
}
// unary expressions
@ -358,38 +350,39 @@ public class EvaluationTests extends ExpressionTestCase {
@Test
public void testTernaryOperator01() {
evaluate("2>4?1:2",2,Integer.class);
evaluate("2>4?1:2", 2, Integer.class);
}
@Test
public void testTernaryOperator02() {
evaluate("'abc'=='abc'?1:2",1,Integer.class);
evaluate("'abc'=='abc'?1:2", 1, Integer.class);
}
@Test
public void testTernaryOperator03() {
evaluateAndCheckError("'hello'?1:2", SpelMessage.TYPE_CONVERSION_ERROR); // cannot convert String to boolean
// cannot convert String to boolean
evaluateAndCheckError("'hello'?1:2", SpelMessage.TYPE_CONVERSION_ERROR);
}
@Test
public void testTernaryOperator04() throws Exception {
Expression expr = parser.parseExpression("1>2?3:4");
Assert.assertFalse(expr.isWritable(eContext));
assertFalse(expr.isWritable(eContext));
}
@Test
public void testTernaryOperator05() {
evaluate("1>2?#var=4:#var=5",5,Integer.class);
evaluate("3?:#var=5",3,Integer.class);
evaluate("null?:#var=5",5,Integer.class);
evaluate("2>4?(3>2?true:false):(5<3?true:false)",false,Boolean.class);
evaluate("1>2?#var=4:#var=5", 5, Integer.class);
evaluate("3?:#var=5", 3, Integer.class);
evaluate("null?:#var=5", 5, Integer.class);
evaluate("2>4?(3>2?true:false):(5<3?true:false)", false, Boolean.class);
}
@Test(expected = EvaluationException.class)
public void testTernaryOperatorWithNullValue() {
parser.parseExpression("null ? 0 : 1").getValue();
}
@Test
public void methodCallWithRootReferenceThroughParameter() {
evaluate("placeOfBirth.doubleIt(inventions.length)", 18, Integer.class);
@ -397,28 +390,29 @@ public class EvaluationTests extends ExpressionTestCase {
@Test
public void ctorCallWithRootReferenceThroughParameter() {
evaluate("new org.springframework.expression.spel.testresources.PlaceOfBirth(inventions[0].toString()).city", "Telephone repeater", String.class);
evaluate("new org.springframework.expression.spel.testresources.PlaceOfBirth(inventions[0].toString()).city",
"Telephone repeater", String.class);
}
@Test
public void fnCallWithRootReferenceThroughParameter() {
evaluate("#reverseInt(inventions.length, inventions.length, inventions.length)", "int[3]{9,9,9}", int[].class);
}
@Test
public void methodCallWithRootReferenceThroughParameterThatIsAFunctionCall() {
evaluate("placeOfBirth.doubleIt(#reverseInt(inventions.length,2,3)[2])", 18, Integer.class);
}
@Test
public void testIndexer03() {
evaluate("'christian'[8]", "n", String.class);
}
@Test
public void testIndexerError() {
evaluateAndCheckError("new org.springframework.expression.spel.testresources.Inventor().inventions[1]",SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE);
evaluateAndCheckError("new org.springframework.expression.spel.testresources.Inventor().inventions[1]",
SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE);
}
@Test
@ -447,16 +441,16 @@ public class EvaluationTests extends ExpressionTestCase {
public void testTypeReferences01() {
evaluate("T(java.lang.String)", "class java.lang.String", Class.class);
}
@Test
public void testTypeReferencesAndQualifiedIdentifierCaching() throws Exception {
SpelExpression expr = (SpelExpression)parser.parseExpression("T(java.lang.String)");
Assert.assertFalse(expr.isWritable(new StandardEvaluationContext()));
Assert.assertEquals("T(java.lang.String)",expr.toStringAST());
Assert.assertEquals(String.class,expr.getValue(Class.class));
SpelExpression expr = (SpelExpression) parser.parseExpression("T(java.lang.String)");
assertFalse(expr.isWritable(new StandardEvaluationContext()));
assertEquals("T(java.lang.String)", expr.toStringAST());
assertEquals(String.class, expr.getValue(Class.class));
// use cached QualifiedIdentifier:
Assert.assertEquals("T(java.lang.String)",expr.toStringAST());
Assert.assertEquals(String.class,expr.getValue(Class.class));
assertEquals("T(java.lang.String)", expr.toStringAST());
assertEquals(String.class, expr.getValue(Class.class));
}
@Test
@ -490,49 +484,50 @@ public class EvaluationTests extends ExpressionTestCase {
evaluateAndAskForReturnType("3*4+5", "17", String.class);
}
@Test
public void testAdvancedNumerics() throws Exception {
int twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Integer.class);
Assert.assertEquals(24,twentyFour);
assertEquals(24, twentyFour);
double one = parser.parseExpression("8.0 / 5e0 % 2").getValue(Double.class);
Assert.assertEquals(1.6d,one);
assertEquals(1.6d, one, 0);
int o = parser.parseExpression("8.0 / 5e0 % 2").getValue(Integer.class);
Assert.assertEquals(1,o);
assertEquals(1, o);
int sixteen = parser.parseExpression("-2 ^ 4").getValue(Integer.class);
Assert.assertEquals(16,sixteen);
assertEquals(16, sixteen);
int minusFortyFive = parser.parseExpression("1+2-3*8^2/2/2").getValue(Integer.class);
Assert.assertEquals(-45,minusFortyFive);
assertEquals(-45, minusFortyFive);
}
@Test
public void testComparison() throws Exception {
EvaluationContext context = TestScenarioCreator.getTestEvaluationContext();
boolean trueValue = parser.parseExpression("T(java.util.Date) == Birthdate.Class").getValue(context, Boolean.class);
Assert.assertTrue(trueValue);
boolean trueValue = parser.parseExpression("T(java.util.Date) == Birthdate.Class").getValue(context,
Boolean.class);
assertTrue(trueValue);
}
@Test
public void testResolvingList() throws Exception {
StandardEvaluationContext context = TestScenarioCreator.getTestEvaluationContext();
try {
Assert.assertFalse(parser.parseExpression("T(List)!=null").getValue(context, Boolean.class));
Assert.fail("should have failed to find List");
assertFalse(parser.parseExpression("T(List)!=null").getValue(context, Boolean.class));
fail("should have failed to find List");
} catch (EvaluationException ee) {
// success - List not found
}
((StandardTypeLocator)context.getTypeLocator()).registerImport("java.util");
Assert.assertTrue(parser.parseExpression("T(List)!=null").getValue(context, Boolean.class));
((StandardTypeLocator) context.getTypeLocator()).registerImport("java.util");
assertTrue(parser.parseExpression("T(List)!=null").getValue(context, Boolean.class));
}
@Test
public void testResolvingString() throws Exception {
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
Assert.assertEquals(String.class,stringClass);
}
Class<?> stringClass = parser.parseExpression("T(String)").getValue(Class.class);
assertEquals(String.class, stringClass);
}
/**
* SPR-6984: attempting to index a collection on write using an index that doesn't currently exist in the collection (address.crossStreets[0] below)
* SPR-6984: attempting to index a collection on write using an index that
* doesn't currently exist in the collection (address.crossStreets[0] below)
*/
@Test
public void initializingCollectionElementsOnWrite() throws Exception {
@ -542,20 +537,38 @@ public class EvaluationTests extends ExpressionTestCase {
ExpressionParser parser = new SpelExpressionParser(config);
Expression expression = parser.parseExpression("name");
expression.setValue(context, "Oleg");
Assert.assertEquals("Oleg",person.getName());
assertEquals("Oleg", person.getName());
expression = parser.parseExpression("address.street");
expression.setValue(context, "123 High St");
Assert.assertEquals("123 High St",person.getAddress().getStreet());
assertEquals("123 High St", person.getAddress().getStreet());
expression = parser.parseExpression("address.crossStreets[0]");
expression.setValue(context, "Blah");
Assert.assertEquals("Blah",person.getAddress().getCrossStreets().get(0));
assertEquals("Blah", person.getAddress().getCrossStreets().get(0));
expression = parser.parseExpression("address.crossStreets[3]");
expression.setValue(context, "Wibble");
Assert.assertEquals("Blah",person.getAddress().getCrossStreets().get(0));
Assert.assertEquals("Wibble",person.getAddress().getCrossStreets().get(3));
assertEquals("Blah", person.getAddress().getCrossStreets().get(0));
assertEquals("Wibble", person.getAddress().getCrossStreets().get(3));
}
/**
* Verifies behavior requested in SPR-9613.
*/
@Test
public void caseInsensitiveNullLiterals() {
ExpressionParser parser = new SpelExpressionParser();
Expression exp;
exp = parser.parseExpression("null");
assertNull(exp.getValue());
exp = parser.parseExpression("NULL");
assertNull(exp.getValue());
exp = parser.parseExpression("NuLl");
assertNull(exp.getValue());
}
}

@ -9,6 +9,7 @@ Changes in version 3.2 M2 (2012-08-xx)
* spring-test module now depends on junit:junit-dep (SPR-6966)
* now inferring return type of parameterized factory methods (SPR-9493)
* SpEL Tokenizer now supports methods on integers (SPR-9612)
* introduced support for case-insensitive null literals in SpEL expressions (SPR-9613)
* now using BufferedInputStream in SimpleMetaDataReader to double performance (SPR-9528)
* introduced "repeatCount" property in Quartz SimpleTriggerFactoryBean (SPR-9521)
* introduced "jtaTransactionManager" property in Hibernate 4 LocalSessionFactoryBean/Builder (SPR-9480)

Loading…
Cancel
Save