diff --git a/spring-context/src/main/java/org/springframework/context/expression/MapAccessor.java b/spring-context/src/main/java/org/springframework/context/expression/MapAccessor.java index ce1b2f9197..0593e26a8b 100644 --- a/spring-context/src/main/java/org/springframework/context/expression/MapAccessor.java +++ b/spring-context/src/main/java/org/springframework/context/expression/MapAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -18,10 +18,12 @@ package org.springframework.context.expression; import java.util.Map; +import org.springframework.asm.MethodVisitor; import org.springframework.expression.AccessException; import org.springframework.expression.EvaluationContext; -import org.springframework.expression.PropertyAccessor; import org.springframework.expression.TypedValue; +import org.springframework.expression.spel.CodeFlow; +import org.springframework.expression.spel.CompilablePropertyAccessor; /** * EL property accessor that knows how to traverse the keys @@ -31,7 +33,7 @@ import org.springframework.expression.TypedValue; * @author Andy Clement * @since 3.0 */ -public class MapAccessor implements PropertyAccessor { +public class MapAccessor implements CompilablePropertyAccessor { @Override public Class[] getSpecificTargetClasses() { @@ -87,4 +89,27 @@ public class MapAccessor implements PropertyAccessor { } } + @Override + public boolean isCompilable() { + return true; + } + + @Override + public Class getPropertyType() { + return Object.class; + } + + @Override + public void generateCode(String propertyName, MethodVisitor mv, CodeFlow cf) { + String descriptor = cf.lastDescriptor(); + if (descriptor == null || !descriptor.equals("Ljava/util/Map")) { + if (descriptor == null) { + cf.loadTarget(mv); + } + CodeFlow.insertCheckCast(mv, "Ljava/util/Map"); + } + mv.visitLdcInsn(propertyName); + mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "get","(Ljava/lang/Object;)Ljava/lang/Object;",true); + } + } diff --git a/spring-context/src/test/java/org/springframework/context/expression/MapAccessorTests.java b/spring-context/src/test/java/org/springframework/context/expression/MapAccessorTests.java new file mode 100644 index 0000000000..db50071cc5 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/expression/MapAccessorTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2015 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.context.expression; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; +import org.springframework.expression.Expression; +import org.springframework.expression.spel.standard.SpelCompiler; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +import static org.junit.Assert.*; + +/** + * Unit tests for compilation of {@link MapAccessor}. + * + * @author Andy Clement + */ +public class MapAccessorTests { + + @Test + public void mapAccessorCompilable() { + Map testMap = getSimpleTestMap(); + StandardEvaluationContext sec = new StandardEvaluationContext(); + sec.addPropertyAccessor(new MapAccessor()); + SpelExpressionParser sep = new SpelExpressionParser(); + + // basic + Expression ex = sep.parseExpression("foo"); + assertEquals("bar",ex.getValue(sec,testMap)); + assertTrue(SpelCompiler.compile(ex)); + assertEquals("bar",ex.getValue(sec,testMap)); + + // compound expression + ex = sep.parseExpression("foo.toUpperCase()"); + assertEquals("BAR",ex.getValue(sec,testMap)); + assertTrue(SpelCompiler.compile(ex)); + assertEquals("BAR",ex.getValue(sec,testMap)); + + // nested map + Map> nestedMap = getNestedTestMap(); + ex = sep.parseExpression("aaa.foo.toUpperCase()"); + assertEquals("BAR",ex.getValue(sec,nestedMap)); + assertTrue(SpelCompiler.compile(ex)); + assertEquals("BAR",ex.getValue(sec,nestedMap)); + + // avoiding inserting checkcast because first part of expression returns a Map + ex = sep.parseExpression("getMap().foo"); + MapGetter mapGetter = new MapGetter(); + assertEquals("bar",ex.getValue(sec,mapGetter)); + assertTrue(SpelCompiler.compile(ex)); + assertEquals("bar",ex.getValue(sec,mapGetter)); + } + + public static class MapGetter { + Map map = new HashMap(); + + public MapGetter() { + map.put("foo", "bar"); + } + + @SuppressWarnings("rawtypes") + public Map getMap() { + return map; + } + } + + public Map getSimpleTestMap() { + Map map = new HashMap(); + map.put("foo","bar"); + return map; + } + + public Map> getNestedTestMap() { + Map map = new HashMap(); + map.put("foo","bar"); + Map> map2 = new HashMap>(); + map2.put("aaa", map); + return map2; + } + +} \ No newline at end of file