diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java index bbe83e977069aef1d94136ec3060f291f452a5c7..7e99f0e987aaae697000b4f8e142b10ede2a25df 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java @@ -25,9 +25,9 @@ import org.springframework.expression.EvaluationContext; import org.springframework.expression.PropertyAccessor; import org.springframework.expression.PropertyReaderExecutor; import org.springframework.expression.PropertyWriterExecutor; +import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; import org.springframework.expression.spel.SpelMessages; -import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.internal.Utils; /** @@ -68,9 +68,9 @@ public class PropertyOrFieldReference extends SpelNode { return name.toString(); } - /** + /** * Attempt to read the named property from the current context object. - * + * * @param state the evaluation state * @param name the name of the property * @return the value of the property @@ -88,19 +88,20 @@ public class PropertyOrFieldReference extends SpelNode { // let's try to get a new one and call it before giving up } } - + Class contextObjectClass = getObjectClass(contextObject); List accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, state); - - // Go through the accessors that may be able to resolve it. If they are a cacheable accessor then - // get the accessor and use it. If they are not cacheable but report they can read the property + + // Go through the accessors that may be able to resolve it. If they are a cacheable accessor then + // get the accessor and use it. If they are not cacheable but report they can read the property // then ask them to read it if (accessorsToTry != null) { try { for (PropertyAccessor accessor : accessorsToTry) { if (accessor instanceof CacheablePropertyAccessor) { - cachedReaderExecutor = ((CacheablePropertyAccessor)accessor).getReaderAccessor(eContext, contextObject, name); + cachedReaderExecutor = ((CacheablePropertyAccessor) accessor).getReaderAccessor(eContext, + contextObject, name); if (cachedReaderExecutor != null) { try { return cachedReaderExecutor.execute(state.getEvaluationContext(), contextObject); @@ -124,14 +125,14 @@ public class PropertyOrFieldReference extends SpelNode { throw new SpelException(ae, SpelMessages.EXCEPTION_DURING_PROPERTY_READ, name, ae.getMessage()); } } - throw new SpelException(SpelMessages.PROPERTY_OR_FIELD_NOT_FOUND, name, Utils.formatClassnameForMessage(contextObjectClass)); + throw new SpelException(SpelMessages.PROPERTY_OR_FIELD_NOT_FOUND, name, Utils + .formatClassnameForMessage(contextObjectClass)); } - private void writeProperty(ExpressionState state, Object name, Object newValue) throws SpelException { Object contextObject = state.getActiveContextObject(); EvaluationContext eContext = state.getEvaluationContext(); - + if (cachedWriterExecutor != null) { try { cachedWriterExecutor.execute(state.getEvaluationContext(), contextObject, newValue); @@ -149,7 +150,8 @@ public class PropertyOrFieldReference extends SpelNode { try { for (PropertyAccessor accessor : accessorsToTry) { if (accessor instanceof CacheablePropertyAccessor) { - cachedWriterExecutor = ((CacheablePropertyAccessor)accessor).getWriterAccessor(eContext, contextObject, name); + cachedWriterExecutor = ((CacheablePropertyAccessor) accessor).getWriterAccessor(eContext, + contextObject, name); if (cachedWriterExecutor != null) { try { cachedWriterExecutor.execute(state.getEvaluationContext(), contextObject, newValue); @@ -171,7 +173,8 @@ public class PropertyOrFieldReference extends SpelNode { } } } catch (AccessException ae) { - throw new SpelException(getCharPositionInLine(), ae, SpelMessages.EXCEPTION_DURING_PROPERTY_WRITE, name, ae.getMessage()); + throw new SpelException(getCharPositionInLine(), ae, SpelMessages.EXCEPTION_DURING_PROPERTY_WRITE, + name, ae.getMessage()); } } throw new SpelException(SpelMessages.PROPERTY_OR_FIELD_SETTER_NOT_FOUND, name, Utils @@ -204,8 +207,8 @@ public class PropertyOrFieldReference extends SpelNode { * Determines the set of property resolvers that should be used to try and access a property on the specified target * type. The resolvers are considered to be in an ordered list, however in the returned list any that are exact * matches for the input target type (as opposed to 'general' resolvers that could work for any type) are placed at - * the start of the list. In addition, there are specific resolvers that exactly name the class in question and - * resolvers that name a specific class but it is a supertype of the class we have. These are put at the end of the + * the start of the list. In addition, there are specific resolvers that exactly name the class in question and + * resolvers that name a specific class but it is a supertype of the class we have. These are put at the end of the * specific resolvers set and will be tried after exactly matching accessors but before generic accessors. * * @param targetType the type upon which property access is being attempted @@ -219,13 +222,16 @@ public class PropertyOrFieldReference extends SpelNode { if (targets == null) { // generic resolver that says it can be used for any type generalAccessors.add(resolver); } else { - int pos = 0; - for (int i = 0; i < targets.length; i++) { - Class clazz = targets[i]; - if (clazz == targetType) { // put exact matches on the front to be tried first? - specificAccessors.add(pos++, resolver); - } else if (clazz.isAssignableFrom(targetType)) { // put supertype matches at the end of the specificAccessor list - generalAccessors.add(resolver); + if (targetType != null) { + int pos = 0; + for (int i = 0; i < targets.length; i++) { + Class clazz = targets[i]; + if (clazz == targetType) { // put exact matches on the front to be tried first? + specificAccessors.add(pos++, resolver); + } else if (clazz.isAssignableFrom(targetType)) { // put supertype matches at the end of the + // specificAccessor list + generalAccessors.add(resolver); + } } } } diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/AllTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/AllTests.java index 8c1837d7d361b19a7f527795ec562b2114e82d03..0ee878560538cd0b4c2da46071269b76599bfdc2 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/AllTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/AllTests.java @@ -48,6 +48,7 @@ public class AllTests { suite.addTestSuite(TemplateExpressionParsingTests.class); suite.addTestSuite(ExpressionLanguageScenarioTests.class); suite.addTestSuite(ScenariosForSpringSecurity.class); + suite.addTestSuite(MapAccessTests.class); suite.addTestSuite(SpelUtilitiesTests.class); suite.addTestSuite(LiteralExpressionTests.class); suite.addTestSuite(CompositeStringExpressionTests.class); diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java index 4960b562bd59176a5c254c438cc5cb19ac2b6aef..833c3bbc18b0a950feac0727cefa1a8e2a8a8532 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java @@ -15,7 +15,6 @@ */ package org.springframework.expression.spel; - /** * Tests the evaluation of real expressions in a real context. * @@ -457,4 +456,5 @@ public class EvaluationTests extends ExpressionTestCase { evaluateAndAskForReturnType("3*4+5", (short) 17, Short.class); evaluateAndAskForReturnType("3*4+5", "17", String.class); } + } diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java new file mode 100644 index 0000000000000000000000000000000000000000..0ed87225ac7191c78533cb9c9e3c427eab39571d --- /dev/null +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2004-2008 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.expression.spel; + +import java.util.Map; + +import org.springframework.expression.AccessException; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.spel.standard.StandardEvaluationContext; + +/** + * Testing variations on map access. + * + * @author Andy Clement + */ +public class MapAccessTests extends ExpressionTestCase { + + static class MapAccessor implements PropertyAccessor { + + public boolean canRead(EvaluationContext context, Object target, Object name) throws AccessException { + return (((Map) target).containsKey(name)); + } + + public Object read(EvaluationContext context, Object target, Object name) throws AccessException { + return ((Map) target).get(name); + } + + public boolean canWrite(EvaluationContext context, Object target, Object name) throws AccessException { + return true; + } + + public void write(EvaluationContext context, Object target, Object name, Object newValue) + throws AccessException { + ((Map) target).put(name, newValue); + } + + public Class[] getSpecificTargetClasses() { + return new Class[] { Map.class }; + } + + } + + public void testSimpleMapAccess01() { + evaluate("testMap.get('monday')", "montag", String.class); + } + + public void testMapAccessThroughIndexer() { + evaluate("testMap['monday']", "montag", String.class); + } + + public void testCustomMapAccessor() throws Exception { + SpelExpressionParser parser = new SpelExpressionParser(); + StandardEvaluationContext ctx = TestScenarioCreator.getTestEvaluationContext(); + ctx.addPropertyAccessor(new MapAccessor()); + + Expression expr = parser.parseExpression("testMap.monday"); + Object value = expr.getValue(ctx, String.class); + assertEquals("montag", value); + } + + public void testVariableMapAccess() throws Exception { + SpelExpressionParser parser = new SpelExpressionParser(); + StandardEvaluationContext ctx = TestScenarioCreator.getTestEvaluationContext(); + ctx.setVariable("day", "saturday"); + + Expression expr = parser.parseExpression("testMap[#day]"); + Object value = expr.getValue(ctx, String.class); + assertEquals("samstag", value); + } + + // public void testMapAccess04() { + // evaluate("testMap[monday]", "montag", String.class); + // } +} diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java index 4cdd8dcba709585f8fe67e95103e7ff6cfa75980..28243534694353c024b7a2d3ca029abe4e0d97cf 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java @@ -1,6 +1,8 @@ package org.springframework.expression.spel.testresources; import java.util.Date; +import java.util.HashMap; +import java.util.Map; @SuppressWarnings("unused") public class Inventor { @@ -11,15 +13,24 @@ public class Inventor { private String nationality; private String[] inventions; public String randomField; + public Map testMap; public Inventor(String name, Date birthdate, String nationality) { this.name = name; this.birthdate = birthdate; this.nationality = nationality; + testMap = new HashMap(); + testMap.put("monday", "montag"); + testMap.put("tuesday", "dienstag"); + testMap.put("wednesday", "mittwoch"); + testMap.put("thursday", "donnerstag"); + testMap.put("friday", "freitag"); + testMap.put("saturday", "samstag"); + testMap.put("sunday", "sonntag"); } public void setPlaceOfBirth(PlaceOfBirth placeOfBirth2) { - this.placeOfBirth = placeOfBirth2; + placeOfBirth = placeOfBirth2; } public void setInventions(String[] inventions) { @@ -45,17 +56,20 @@ public class Inventor { public String joinThreeStrings(String a, String b, String c) { return a + b + c; } - - public int aVarargsMethod(String...strings ) { - if (strings==null) return 0; + + public int aVarargsMethod(String... strings) { + if (strings == null) + return 0; return strings.length; } - public int aVarargsMethod2(int i, String...strings ) { - if (strings==null) return i; - return strings.length+i; + + public int aVarargsMethod2(int i, String... strings) { + if (strings == null) + return i; + return strings.length + i; } - public Inventor(String...strings ) { - + public Inventor(String... strings) { + } }