diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java index ec43b2bfab..84c3c27350 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java @@ -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(); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java index bb50bfeb0b..30d637ddd8 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java @@ -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 mapStringToInteger; + public Map mapStringToInteger; public List list; public List list2; private Map map2; - + private List foo; + public Map getMap2() { return this.map2; } public Foo getWibble2() { return this.wibble2; } -// public void setMap2(Map m) { this.map2 = m; } - private List foo; public List getFoo() { return this.foo; } public void setFoo(List 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()); + } + } diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt index e5a777baa1..9f1766ee2e 100644 --- a/src/dist/changelog.txt +++ b/src/dist/changelog.txt @@ -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)