From 29303ef5916a0e05e3120287792eef6d479f5c3e Mon Sep 17 00:00:00 2001 From: Andy Clement Date: Mon, 23 Nov 2015 09:25:57 -0800 Subject: [PATCH] Allow MapAccessor to be compilable in SpEL expressions With this change the MapAccessor now extends CompilablePropertyAccessor rather than just PropertyAccessor. This means that any expression that ends up using the MapAccessor is now compilable for fast performance. Issue: SPR-13638 --- .../context/expression/MapAccessor.java | 31 +++++- .../context/expression/MapAccessorTests.java | 98 +++++++++++++++++++ 2 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 spring-context/src/test/java/org/springframework/context/expression/MapAccessorTests.java 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