Merge pull request #113 from aclement/SPR-9613

* SPR-9613:
  Support case-insensitive null literals in SpEL
This commit is contained in:
Sam Brannen 2012-08-04 01:50:36 +02:00
commit 3fbcebb82e
3 changed files with 134 additions and 120 deletions

View File

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

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,12 +16,13 @@
package org.springframework.expression.spel; package org.springframework.expression.spel;
import static org.junit.Assert.*;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import junit.framework.Assert;
import org.junit.Test; import org.junit.Test;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException; import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression; import org.springframework.expression.Expression;
@ -38,6 +39,7 @@ import org.springframework.expression.spel.testresources.TestPerson;
* *
* @author Andy Clement * @author Andy Clement
* @author Mark Fisher * @author Mark Fisher
* @author Sam Brannen
* @since 3.0 * @since 3.0
*/ */
public class EvaluationTests extends ExpressionTestCase { public class EvaluationTests extends ExpressionTestCase {
@ -49,62 +51,53 @@ public class EvaluationTests extends ExpressionTestCase {
TestClass testClass = new TestClass(); TestClass testClass = new TestClass();
Object o = null; Object o = null;
o = expression.getValue(new StandardEvaluationContext(testClass)); o = expression.getValue(new StandardEvaluationContext(testClass));
Assert.assertEquals("",o); assertEquals("", o);
o = parser.parseExpression("list[3]").getValue(new StandardEvaluationContext(testClass)); o = parser.parseExpression("list[3]").getValue(new StandardEvaluationContext(testClass));
Assert.assertEquals("",o); assertEquals("", o);
Assert.assertEquals(4, testClass.list.size()); assertEquals(4, testClass.list.size());
try { try {
o = parser.parseExpression("list2[3]").getValue(new StandardEvaluationContext(testClass)); o = parser.parseExpression("list2[3]").getValue(new StandardEvaluationContext(testClass));
Assert.fail(); fail();
} catch (EvaluationException ee) { } catch (EvaluationException ee) {
ee.printStackTrace(); ee.printStackTrace();
// success! // success!
} }
o = parser.parseExpression("foo[3]").getValue(new StandardEvaluationContext(testClass)); o = parser.parseExpression("foo[3]").getValue(new StandardEvaluationContext(testClass));
Assert.assertEquals("",o); assertEquals("", o);
Assert.assertEquals(4, testClass.getFoo().size()); assertEquals(4, testClass.getFoo().size());
} }
@Test @Test(expected = SpelEvaluationException.class)
public void testCreateMapsOnAttemptToIndexNull01() throws EvaluationException, ParseException { public void testCreateMapsOnAttemptToIndexNull01() throws Exception {
TestClass testClass = new TestClass(); TestClass testClass = new TestClass();
StandardEvaluationContext ctx = new StandardEvaluationContext(testClass); StandardEvaluationContext ctx = new StandardEvaluationContext(testClass);
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
Object o = null; Object o = null;
o = parser.parseExpression("map['a']").getValue(ctx); o = parser.parseExpression("map['a']").getValue(ctx);
Assert.assertNull(o); assertNull(o);
o = parser.parseExpression("map").getValue(ctx); o = parser.parseExpression("map").getValue(ctx);
Assert.assertNotNull(o); assertNotNull(o);
try {
o = parser.parseExpression("map2['a']").getValue(ctx); o = parser.parseExpression("map2['a']").getValue(ctx);
// fail! // map2 should be null, there is no setter
Assert.fail("map2 should be null, there is no setter");
} catch (Exception e) {
// success!
}
} }
@Test // wibble2 should be null (cannot be initialized dynamically), there is no setter
public void testCreateObjectsOnAttemptToReferenceNull() throws EvaluationException, ParseException { @Test(expected = SpelEvaluationException.class)
public void testCreateObjectsOnAttemptToReferenceNull() throws Exception {
TestClass testClass = new TestClass(); TestClass testClass = new TestClass();
StandardEvaluationContext ctx = new StandardEvaluationContext(testClass); StandardEvaluationContext ctx = new StandardEvaluationContext(testClass);
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
Object o = null; Object o = null;
o = parser.parseExpression("wibble.bar").getValue(ctx); o = parser.parseExpression("wibble.bar").getValue(ctx);
Assert.assertEquals("hello",o); assertEquals("hello", o);
o = parser.parseExpression("wibble").getValue(ctx); o = parser.parseExpression("wibble").getValue(ctx);
Assert.assertNotNull(o); assertNotNull(o);
try {
o = parser.parseExpression("wibble2.bar").getValue(ctx); 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!
}
} }
static class TestClass { static class TestClass {
public Foo wibble; public Foo wibble;
@ -114,11 +107,10 @@ public class EvaluationTests extends ExpressionTestCase {
public List<String> list; public List<String> list;
public List list2; public List list2;
private Map map2; private Map map2;
private List<String> foo;
public Map getMap2() { return this.map2; } public Map getMap2() { return this.map2; }
public Foo getWibble2() { return this.wibble2; } 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 List<String> getFoo() { return this.foo; }
public void setFoo(List<String> newfoo) { this.foo = newfoo; } public void setFoo(List<String> newfoo) { this.foo = newfoo; }
} }
@ -128,6 +120,7 @@ public class EvaluationTests extends ExpressionTestCase {
public String bar = "hello"; public String bar = "hello";
} }
@Test @Test
public void testElvis01() { public void testElvis01() {
evaluate("'Andy'?:'Dave'", "Andy", String.class); evaluate("'Andy'?:'Dave'", "Andy", String.class);
@ -234,16 +227,15 @@ public class EvaluationTests extends ExpressionTestCase {
public void testRogueTrailingDotCausesNPE_SPR6866() { public void testRogueTrailingDotCausesNPE_SPR6866() {
try { try {
new SpelExpressionParser().parseExpression("placeOfBirth.foo."); new SpelExpressionParser().parseExpression("placeOfBirth.foo.");
Assert.fail("Should have failed to parse"); fail("Should have failed to parse");
} catch (ParseException e) { } catch (ParseException e) {
Assert.assertTrue(e instanceof SpelParseException); assertTrue(e instanceof SpelParseException);
SpelParseException spe = (SpelParseException) e; SpelParseException spe = (SpelParseException) e;
Assert.assertEquals(SpelMessage.OOD,spe.getMessageCode()); assertEquals(SpelMessage.OOD, spe.getMessageCode());
Assert.assertEquals(16,spe.getPosition()); assertEquals(16, spe.getPosition());
} }
} }
// nested properties // nested properties
@Test @Test
public void testPropertiesNested01() { public void testPropertiesNested01() {
@ -259,10 +251,10 @@ public class EvaluationTests extends ExpressionTestCase {
public void testPropertiesNested03() throws ParseException { public void testPropertiesNested03() throws ParseException {
try { try {
new SpelExpressionParser().parseRaw("placeOfBirth.23"); new SpelExpressionParser().parseRaw("placeOfBirth.23");
Assert.fail(); fail();
} catch (SpelParseException spe) { } catch (SpelParseException spe) {
Assert.assertEquals(spe.getMessageCode(), SpelMessage.UNEXPECTED_DATA_AFTER_DOT); assertEquals(spe.getMessageCode(), SpelMessage.UNEXPECTED_DATA_AFTER_DOT);
Assert.assertEquals("23", spe.getInserts()[0]); assertEquals("23", spe.getInserts()[0]);
} }
} }
@ -293,15 +285,15 @@ public class EvaluationTests extends ExpressionTestCase {
// repeated evaluation to drive use of cached executor // 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); String newString = expr.getValue(String.class);
Assert.assertEquals("wibble",newString); assertEquals("wibble", newString);
newString = expr.getValue(String.class); newString = expr.getValue(String.class);
Assert.assertEquals("wibble",newString); assertEquals("wibble", newString);
// not writable // not writable
Assert.assertFalse(expr.isWritable(new StandardEvaluationContext())); assertFalse(expr.isWritable(new StandardEvaluationContext()));
// ast // ast
Assert.assertEquals("new String('wibble')",expr.toStringAST()); assertEquals("new String('wibble')", expr.toStringAST());
} }
// unary expressions // unary expressions
@ -368,13 +360,14 @@ public class EvaluationTests extends ExpressionTestCase {
@Test @Test
public void testTernaryOperator03() { 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 @Test
public void testTernaryOperator04() throws Exception { public void testTernaryOperator04() throws Exception {
Expression expr = parser.parseExpression("1>2?3:4"); Expression expr = parser.parseExpression("1>2?3:4");
Assert.assertFalse(expr.isWritable(eContext)); assertFalse(expr.isWritable(eContext));
} }
@Test @Test
@ -397,7 +390,8 @@ public class EvaluationTests extends ExpressionTestCase {
@Test @Test
public void ctorCallWithRootReferenceThroughParameter() { 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 @Test
@ -415,10 +409,10 @@ public class EvaluationTests extends ExpressionTestCase {
evaluate("'christian'[8]", "n", String.class); evaluate("'christian'[8]", "n", String.class);
} }
@Test @Test
public void testIndexerError() { 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 @Test
@ -451,12 +445,12 @@ public class EvaluationTests extends ExpressionTestCase {
@Test @Test
public void testTypeReferencesAndQualifiedIdentifierCaching() throws Exception { public void testTypeReferencesAndQualifiedIdentifierCaching() throws Exception {
SpelExpression expr = (SpelExpression) parser.parseExpression("T(java.lang.String)"); SpelExpression expr = (SpelExpression) parser.parseExpression("T(java.lang.String)");
Assert.assertFalse(expr.isWritable(new StandardEvaluationContext())); assertFalse(expr.isWritable(new StandardEvaluationContext()));
Assert.assertEquals("T(java.lang.String)",expr.toStringAST()); assertEquals("T(java.lang.String)", expr.toStringAST());
Assert.assertEquals(String.class,expr.getValue(Class.class)); assertEquals(String.class, expr.getValue(Class.class));
// use cached QualifiedIdentifier: // use cached QualifiedIdentifier:
Assert.assertEquals("T(java.lang.String)",expr.toStringAST()); assertEquals("T(java.lang.String)", expr.toStringAST());
Assert.assertEquals(String.class,expr.getValue(Class.class)); assertEquals(String.class, expr.getValue(Class.class));
} }
@Test @Test
@ -490,49 +484,50 @@ public class EvaluationTests extends ExpressionTestCase {
evaluateAndAskForReturnType("3*4+5", "17", String.class); evaluateAndAskForReturnType("3*4+5", "17", String.class);
} }
@Test @Test
public void testAdvancedNumerics() throws Exception { public void testAdvancedNumerics() throws Exception {
int twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Integer.class); 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); 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); 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); 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); int minusFortyFive = parser.parseExpression("1+2-3*8^2/2/2").getValue(Integer.class);
Assert.assertEquals(-45,minusFortyFive); assertEquals(-45, minusFortyFive);
} }
@Test @Test
public void testComparison() throws Exception { public void testComparison() throws Exception {
EvaluationContext context = TestScenarioCreator.getTestEvaluationContext(); EvaluationContext context = TestScenarioCreator.getTestEvaluationContext();
boolean trueValue = parser.parseExpression("T(java.util.Date) == Birthdate.Class").getValue(context, Boolean.class); boolean trueValue = parser.parseExpression("T(java.util.Date) == Birthdate.Class").getValue(context,
Assert.assertTrue(trueValue); Boolean.class);
assertTrue(trueValue);
} }
@Test @Test
public void testResolvingList() throws Exception { public void testResolvingList() throws Exception {
StandardEvaluationContext context = TestScenarioCreator.getTestEvaluationContext(); StandardEvaluationContext context = TestScenarioCreator.getTestEvaluationContext();
try { try {
Assert.assertFalse(parser.parseExpression("T(List)!=null").getValue(context, Boolean.class)); assertFalse(parser.parseExpression("T(List)!=null").getValue(context, Boolean.class));
Assert.fail("should have failed to find List"); fail("should have failed to find List");
} catch (EvaluationException ee) { } catch (EvaluationException ee) {
// success - List not found // success - List not found
} }
((StandardTypeLocator) context.getTypeLocator()).registerImport("java.util"); ((StandardTypeLocator) context.getTypeLocator()).registerImport("java.util");
Assert.assertTrue(parser.parseExpression("T(List)!=null").getValue(context, Boolean.class)); assertTrue(parser.parseExpression("T(List)!=null").getValue(context, Boolean.class));
} }
@Test @Test
public void testResolvingString() throws Exception { public void testResolvingString() throws Exception {
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class); Class<?> stringClass = parser.parseExpression("T(String)").getValue(Class.class);
Assert.assertEquals(String.class,stringClass); 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 @Test
public void initializingCollectionElementsOnWrite() throws Exception { public void initializingCollectionElementsOnWrite() throws Exception {
@ -542,20 +537,38 @@ public class EvaluationTests extends ExpressionTestCase {
ExpressionParser parser = new SpelExpressionParser(config); ExpressionParser parser = new SpelExpressionParser(config);
Expression expression = parser.parseExpression("name"); Expression expression = parser.parseExpression("name");
expression.setValue(context, "Oleg"); expression.setValue(context, "Oleg");
Assert.assertEquals("Oleg",person.getName()); assertEquals("Oleg", person.getName());
expression = parser.parseExpression("address.street"); expression = parser.parseExpression("address.street");
expression.setValue(context, "123 High St"); 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 = parser.parseExpression("address.crossStreets[0]");
expression.setValue(context, "Blah"); 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 = parser.parseExpression("address.crossStreets[3]");
expression.setValue(context, "Wibble"); expression.setValue(context, "Wibble");
Assert.assertEquals("Blah",person.getAddress().getCrossStreets().get(0)); assertEquals("Blah", person.getAddress().getCrossStreets().get(0));
Assert.assertEquals("Wibble",person.getAddress().getCrossStreets().get(3)); 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());
} }
} }

View File

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