From c7fd03be6923e572fc50dca880e40201b2ad1cf8 Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Wed, 1 Feb 2012 17:19:42 +0100 Subject: [PATCH 1/2] Polish ReflectiveMethodResolver and unit tests - Update Javadoc - Fix whitespace errors (tabs v. spaces, trailing spaces) - Break at 90 chars where sensible - Remove dead test code - Fix generics warnings, remove @SuppressWarnings --- .../support/ReflectiveMethodResolver.java | 27 +- .../expression/spel/SpringEL300Tests.java | 583 ++++++++---------- 2 files changed, 279 insertions(+), 331 deletions(-) diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java index d40daa3cbae..019faf2836b 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java @@ -38,27 +38,30 @@ import org.springframework.expression.spel.SpelMessage; import org.springframework.util.CollectionUtils; /** - * A method resolver that uses reflection to locate the method that should be invoked. + * Reflection-based {@link MethodResolver} used by default in + * {@link StandardEvaluationContext} unless explicit method resolvers have been specified. * * @author Andy Clement * @author Juergen Hoeller + * @author Chris Beams * @since 3.0 + * @see StandardEvaluationContext#addMethodResolver(MethodResolver) */ public class ReflectiveMethodResolver implements MethodResolver { private static Method[] NO_METHODS = new Method[0]; private Map, MethodFilter> filters = null; - + // Using distance will ensure a more accurate match is discovered, // more closely following the Java rules. private boolean useDistance = false; - + public ReflectiveMethodResolver() { - + } - + /** * This constructors allows the ReflectiveMethodResolver to be configured such that it will * use a distance computation to check which is the better of two close matches (when there @@ -71,7 +74,7 @@ public class ReflectiveMethodResolver implements MethodResolver { public ReflectiveMethodResolver(boolean useDistance) { this.useDistance = useDistance; } - + /** * Locate a method on a type. There are three kinds of match that might occur: *
    @@ -88,14 +91,14 @@ public class ReflectiveMethodResolver implements MethodResolver { TypeConverter typeConverter = context.getTypeConverter(); Class type = (targetObject instanceof Class ? (Class) targetObject : targetObject.getClass()); Method[] methods = type.getMethods(); - + // If a filter is registered for this type, call it MethodFilter filter = (this.filters != null ? this.filters.get(type) : null); if (filter != null) { - List methodsForFiltering = new ArrayList(); - for (Method method: methods) { - methodsForFiltering.add(method); - } + List methodsForFiltering = new ArrayList(); + for (Method method: methods) { + methodsForFiltering.add(method); + } List methodsFiltered = filter.filter(methodsForFiltering); if (CollectionUtils.isEmpty(methodsFiltered)) { methods = NO_METHODS; @@ -124,7 +127,7 @@ public class ReflectiveMethodResolver implements MethodResolver { continue; } if (method.getName().equals(name)) { - Class[] paramTypes = method.getParameterTypes(); + Class[] paramTypes = method.getParameterTypes(); List paramDescriptors = new ArrayList(paramTypes.length); for (int i = 0; i < paramTypes.length; i++) { paramDescriptors.add(new TypeDescriptor(new MethodParameter(method, i))); diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java index c947e27e025..e28f9dd3a15 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java @@ -50,12 +50,12 @@ import org.springframework.expression.spel.support.StandardTypeLocator; /** * Tests based on Jiras up to the release of Spring 3.0.0 - * + * * @author Andy Clement * @author Clark Duplichien */ public class SpringEL300Tests extends ExpressionTestCase { - + @Test public void testNPE_SPR5661() { evaluate("joinThreeStrings('a',null,'c')", "anullc", String.class); @@ -66,7 +66,7 @@ public class SpringEL300Tests extends ExpressionTestCase { public void testSWF1086() { evaluate("printDouble(T(java.math.BigDecimal).valueOf(14.35))", "anullc", String.class); } - + @Test public void testDoubleCoercion() { evaluate("printDouble(14.35)", "14.35", String.class); @@ -92,15 +92,15 @@ public class SpringEL300Tests extends ExpressionTestCase { // success } eContext.setTypeLocator(new MyTypeLocator()); - + // varargs expr = new SpelExpressionParser().parseRaw("tryToInvokeWithNull3(null,'a','b')"); Assert.assertEquals("ab",expr.getValue(eContext)); - + // varargs 2 - null is packed into the varargs expr = new SpelExpressionParser().parseRaw("tryToInvokeWithNull3(12,'a',null,'c')"); Assert.assertEquals("anullc",expr.getValue(eContext)); - + // check we can find the ctor ok expr = new SpelExpressionParser().parseRaw("new Spr5899Class().toString()"); Assert.assertEquals("instance",expr.getValue(eContext)); @@ -116,7 +116,7 @@ public class SpringEL300Tests extends ExpressionTestCase { expr = new SpelExpressionParser().parseRaw("new Spr5899Class(null,'a', null, 'b').toString()"); Assert.assertEquals("instance",expr.getValue(eContext)); } - + static class MyTypeLocator extends StandardTypeLocator { public Class findType(String typename) throws EvaluationException { @@ -132,12 +132,12 @@ public class SpringEL300Tests extends ExpressionTestCase { static class Spr5899Class { public Spr5899Class() {} - public Spr5899Class(Integer i) { } - public Spr5899Class(Integer i, String... s) { } - + public Spr5899Class(Integer i) { } + public Spr5899Class(Integer i, String... s) { } + public Integer tryToInvokeWithNull(Integer value) { return value; } public Integer tryToInvokeWithNull2(int i) { return new Integer(i); } - public String tryToInvokeWithNull3(Integer value,String... strings) { + public String tryToInvokeWithNull3(Integer value,String... strings) { StringBuilder sb = new StringBuilder(); for (int i=0;i m = new HashMap(); m.put("foo", "bar"); StandardEvaluationContext eContext = new StandardEvaluationContext(m); // root is a map instance eContext.addPropertyAccessor(new MapAccessor()); Expression expr = new SpelExpressionParser().parseRaw("['foo']"); Assert.assertEquals("bar", expr.getValue(eContext)); } - + @Test public void testSPR5847() throws Exception { StandardEvaluationContext eContext = new StandardEvaluationContext(new TestProperties()); String name = null; Expression expr = null; - + expr = new SpelExpressionParser().parseRaw("jdbcProperties['username']"); name = expr.getValue(eContext,String.class); Assert.assertEquals("Dave",name); - + expr = new SpelExpressionParser().parseRaw("jdbcProperties[username]"); name = expr.getValue(eContext,String.class); Assert.assertEquals("Dave",name); @@ -209,7 +208,7 @@ public class SpringEL300Tests extends ExpressionTestCase { eContext.addPropertyAccessor(new MapAccessor()); name = expr.getValue(eContext,String.class); Assert.assertEquals("Dave",name); - + // --- dotted property names // lookup foo on the root, then bar on that, then use that as the key into jdbcProperties @@ -222,9 +221,9 @@ public class SpringEL300Tests extends ExpressionTestCase { expr = new SpelExpressionParser().parseRaw("jdbcProperties['foo.bar']"); eContext.addPropertyAccessor(new MapAccessor()); name = expr.getValue(eContext,String.class); - Assert.assertEquals("Elephant",name); + Assert.assertEquals("Elephant",name); } - + static class TestProperties { public Properties jdbcProperties = new Properties(); public Properties foo = new Properties(); @@ -239,11 +238,11 @@ public class SpringEL300Tests extends ExpressionTestCase { static class MapAccessor implements PropertyAccessor { public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException { - return (((Map) target).containsKey(name)); + return (((Map) target).containsKey(name)); } public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { - return new TypedValue(((Map) target).get(name)); + return new TypedValue(((Map) target).get(name)); } public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { @@ -252,22 +251,22 @@ public class SpringEL300Tests extends ExpressionTestCase { @SuppressWarnings("unchecked") public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException { - ((Map) target).put(name, newValue); + ((Map) target).put(name, newValue); } - public Class[] getSpecificTargetClasses() { + public Class[] getSpecificTargetClasses() { return new Class[] {Map.class}; } - + } - + @Test public void testNPE_SPR5673() throws Exception { ParserContext hashes = TemplateExpressionParsingTests.HASH_DELIMITED_PARSER_CONTEXT; ParserContext dollars = TemplateExpressionParsingTests.DEFAULT_TEMPLATE_PARSER_CONTEXT; - + checkTemplateParsing("abc${'def'} ghi","abcdef ghi"); - + checkTemplateParsingError("abc${ {}( 'abc'","Missing closing ')' for '(' at position 8"); checkTemplateParsingError("abc${ {}[ 'abc'","Missing closing ']' for '[' at position 8"); checkTemplateParsingError("abc${ {}{ 'abc'","Missing closing '}' for '{' at position 8"); @@ -278,7 +277,7 @@ public class SpringEL300Tests extends ExpressionTestCase { checkTemplateParsingError("abc${ ] }","Found closing ']' at position 6 without an opening '['"); checkTemplateParsingError("abc${ } }","No expression defined within delimiter '${}' at character 3"); checkTemplateParsingError("abc$[ } ]",DOLLARSQUARE_TEMPLATE_PARSER_CONTEXT,"Found closing '}' at position 6 without an opening '{'"); - + checkTemplateParsing("abc ${\"def''g}hi\"} jkl","abc def'g}hi jkl"); checkTemplateParsing("abc ${'def''g}hi'} jkl","abc def'g}hi jkl"); checkTemplateParsing("}","}"); @@ -295,7 +294,7 @@ public class SpringEL300Tests extends ExpressionTestCase { checkTemplateParsing("Hello ${'inner literal that''s got {[(])]}an escaped quote in it'} World","Hello inner literal that's got {[(])]}an escaped quote in it World"); checkTemplateParsingError("Hello ${","No ending suffix '}' for expression starting at character 6: ${"); } - + @Test public void testAccessingNullPropertyViaReflection_SPR5663() throws AccessException { PropertyAccessor propertyAccessor = new ReflectivePropertyAccessor(); @@ -315,33 +314,33 @@ public class SpringEL300Tests extends ExpressionTestCase { // success } } - + @Test public void testNestedProperties_SPR6923() { StandardEvaluationContext eContext = new StandardEvaluationContext(new Foo()); String name = null; Expression expr = null; - + expr = new SpelExpressionParser().parseRaw("resource.resource.server"); name = expr.getValue(eContext,String.class); Assert.assertEquals("abc",name); } - + static class Foo { public ResourceSummary resource = new ResourceSummary(); } - + static class ResourceSummary { ResourceSummary() { this.resource = new Resource(); } private final Resource resource; public Resource getResource() { - return resource; - } + return resource; + } } - + static class Resource { public String getServer() { return "abc"; @@ -374,9 +373,8 @@ public class SpringEL300Tests extends ExpressionTestCase { name = expr.getValue(eContext,String.class); // will be using the cached accessor this time Assert.assertEquals("hello",name); } - + /** $ related identifiers */ - @SuppressWarnings("unchecked") @Test public void testDollarPrefixedIdentifier_SPR7100() { Holder h = new Holder(); @@ -390,7 +388,7 @@ public class SpringEL300Tests extends ExpressionTestCase { h.map.put("$_$","tribble"); String name = null; Expression expr = null; - + expr = new SpelExpressionParser().parseRaw("map.$foo"); name = expr.getValue(eContext,String.class); Assert.assertEquals("wibble",name); @@ -430,8 +428,11 @@ public class SpringEL300Tests extends ExpressionTestCase { name = expr.getValue(eContext,String.class); // will be using the cached accessor this time Assert.assertEquals("wobble",name); } - - /** Should be accessing (setting) Goo.wibble field because 'bar' variable evaluates to "wibble" */ + + /** + * Should be accessing (setting) Goo.wibble field because 'bar' variable evaluates to + * "wibble" + */ @Test public void testIndexingAsAPropertyAccess_SPR6968_4() { Goo g = Goo.instance; @@ -458,7 +459,7 @@ public class SpringEL300Tests extends ExpressionTestCase { expr.getValue(eContext,String.class); // will be using the cached accessor this time Assert.assertEquals("world",g.value); } - + @Test public void testDollars() { StandardEvaluationContext eContext = new StandardEvaluationContext(new XX()); @@ -467,7 +468,7 @@ public class SpringEL300Tests extends ExpressionTestCase { eContext.setVariable("file_name","$foo"); Assert.assertEquals("wibble",expr.getValue(eContext,String.class)); } - + @Test public void testDollars2() { StandardEvaluationContext eContext = new StandardEvaluationContext(new XX()); @@ -476,12 +477,12 @@ public class SpringEL300Tests extends ExpressionTestCase { eContext.setVariable("file_name","$foo"); Assert.assertEquals("wibble",expr.getValue(eContext,String.class)); } - + static class XX { public Map m; - + public String floo ="bar"; - + public XX() { m = new HashMap(); m.put("$foo","wibble"); @@ -490,34 +491,34 @@ public class SpringEL300Tests extends ExpressionTestCase { } static class Goo { - + public static Goo instance = new Goo(); public String bar = "key"; public String value = null; public String wibble = "wobble"; - + public String getKey() { return "hello"; } - + public void setKey(String s) { value = s; } - + } - + static class Holder { - - public Map map = new HashMap(); + + public Map map = new HashMap(); } - + // --- private void checkTemplateParsing(String expression, String expectedValue) throws Exception { checkTemplateParsing(expression,TemplateExpressionParsingTests.DEFAULT_TEMPLATE_PARSER_CONTEXT, expectedValue); } - + private void checkTemplateParsing(String expression, ParserContext context, String expectedValue) throws Exception { SpelExpressionParser parser = new SpelExpressionParser(); Expression expr = parser.parseExpression(expression,context); @@ -527,7 +528,7 @@ public class SpringEL300Tests extends ExpressionTestCase { private void checkTemplateParsingError(String expression,String expectedMessage) throws Exception { checkTemplateParsingError(expression, TemplateExpressionParsingTests.DEFAULT_TEMPLATE_PARSER_CONTEXT,expectedMessage); } - + private void checkTemplateParsingError(String expression,ParserContext context, String expectedMessage) throws Exception { SpelExpressionParser parser = new SpelExpressionParser(); try { @@ -540,7 +541,7 @@ public class SpringEL300Tests extends ExpressionTestCase { Assert.assertEquals(expectedMessage,e.getMessage()); } } - + private static final ParserContext DOLLARSQUARE_TEMPLATE_PARSER_CONTEXT = new ParserContext() { public String getExpressionPrefix() { return "$["; @@ -552,28 +553,13 @@ public class SpringEL300Tests extends ExpressionTestCase { return true; } }; - -// @Test -// public void testFails() { -// -// StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); -// evaluationContext.setVariable("target", new Foo2()); -// for (int i = 0; i < 300000; i++) { -// evaluationContext.addPropertyAccessor(new MapAccessor()); -// ExpressionParser parser = new SpelExpressionParser(); -// Expression expression = parser.parseExpression("#target.execute(payload)"); -// Message message = new Message(); -// message.setPayload(i+""); -// expression.getValue(evaluationContext, message); -// } -// } - + static class Foo2 { public void execute(String str){ System.out.println("Value: " + str); } } - + static class Message{ private String payload; @@ -587,7 +573,7 @@ public class SpringEL300Tests extends ExpressionTestCase { } // bean resolver tests - + @Test public void beanResolution() { StandardEvaluationContext eContext = new StandardEvaluationContext(new XX()); @@ -601,7 +587,7 @@ public class SpringEL300Tests extends ExpressionTestCase { Assert.assertEquals(SpelMessage.NO_BEAN_RESOLVER_REGISTERED,see.getMessageCode()); Assert.assertEquals("foo",see.getInserts()[0]); } - + eContext.setBeanResolver(new MyBeanResolver()); // bean exists @@ -622,11 +608,11 @@ public class SpringEL300Tests extends ExpressionTestCase { Assert.assertTrue(see.getCause() instanceof AccessException); Assert.assertTrue(((AccessException)see.getCause()).getMessage().startsWith("DONT")); } - + // bean exists expr = new SpelExpressionParser().parseRaw("@'foo.bar'"); Assert.assertEquals("trouble",expr.getValue(eContext,String.class)); - + // bean exists try { expr = new SpelExpressionParser().parseRaw("@378"); @@ -635,7 +621,7 @@ public class SpringEL300Tests extends ExpressionTestCase { Assert.assertEquals(SpelMessage.INVALID_BEAN_REFERENCE,spe.getMessageCode()); } } - + static class MyBeanResolver implements BeanResolver { public Object resolve(EvaluationContext context, String beanname) throws AccessException { if (beanname.equals("foo")) { @@ -648,14 +634,14 @@ public class SpringEL300Tests extends ExpressionTestCase { return null; } } - + // end bean resolver tests @Test public void elvis_SPR7209_1() { StandardEvaluationContext eContext = new StandardEvaluationContext(new XX()); Expression expr = null; - + // Different parts of elvis expression are null expr = new SpelExpressionParser().parseRaw("(?:'default')"); Assert.assertEquals("default", expr.getValue()); @@ -696,68 +682,67 @@ public class SpringEL300Tests extends ExpressionTestCase { expr = new SpelExpressionParser().parseRaw("''?:'default'"); Assert.assertEquals("default", expr.getValue()); } - + @Test - @SuppressWarnings("unchecked") public void testMapOfMap_SPR7244() throws Exception { - Map map = new LinkedHashMap(); - map.put("uri", "http:"); - Map nameMap = new LinkedHashMap(); - nameMap.put("givenName", "Arthur"); - map.put("value", nameMap); - - StandardEvaluationContext ctx = new StandardEvaluationContext(map); - ExpressionParser parser = new SpelExpressionParser(); - String el1 = "#root['value'].get('givenName')"; - Expression exp = parser.parseExpression(el1); - Object evaluated = exp.getValue(ctx); - Assert.assertEquals("Arthur", evaluated); - - String el2 = "#root['value']['givenName']"; - exp = parser.parseExpression(el2); - evaluated = exp.getValue(ctx); - Assert.assertEquals("Arthur",evaluated); - } - + Map map = new LinkedHashMap(); + map.put("uri", "http:"); + Map nameMap = new LinkedHashMap(); + nameMap.put("givenName", "Arthur"); + map.put("value", nameMap); + + StandardEvaluationContext ctx = new StandardEvaluationContext(map); + ExpressionParser parser = new SpelExpressionParser(); + String el1 = "#root['value'].get('givenName')"; + Expression exp = parser.parseExpression(el1); + Object evaluated = exp.getValue(ctx); + Assert.assertEquals("Arthur", evaluated); + + String el2 = "#root['value']['givenName']"; + exp = parser.parseExpression(el2); + evaluated = exp.getValue(ctx); + Assert.assertEquals("Arthur",evaluated); + } + @Test public void testProjectionTypeDescriptors_1() throws Exception { - StandardEvaluationContext ctx = new StandardEvaluationContext(new C()); - SpelExpressionParser parser = new SpelExpressionParser(); - String el1 = "ls.![#this.equals('abc')]"; - SpelExpression exp = parser.parseRaw(el1); - List value = (List)exp.getValue(ctx); - // value is list containing [true,false] - Assert.assertEquals(Boolean.class,value.get(0).getClass()); - TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx); - Assert.assertEquals(null, evaluated.getElementTypeDescriptor()); + StandardEvaluationContext ctx = new StandardEvaluationContext(new C()); + SpelExpressionParser parser = new SpelExpressionParser(); + String el1 = "ls.![#this.equals('abc')]"; + SpelExpression exp = parser.parseRaw(el1); + List value = (List)exp.getValue(ctx); + // value is list containing [true,false] + Assert.assertEquals(Boolean.class,value.get(0).getClass()); + TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx); + Assert.assertEquals(null, evaluated.getElementTypeDescriptor()); } - + @Test public void testProjectionTypeDescriptors_2() throws Exception { - StandardEvaluationContext ctx = new StandardEvaluationContext(new C()); - SpelExpressionParser parser = new SpelExpressionParser(); - String el1 = "as.![#this.equals('abc')]"; - SpelExpression exp = parser.parseRaw(el1); - Object[] value = (Object[])exp.getValue(ctx); - // value is array containing [true,false] - Assert.assertEquals(Boolean.class,value[0].getClass()); - TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx); - Assert.assertEquals(Boolean.class, evaluated.getElementTypeDescriptor().getType()); + StandardEvaluationContext ctx = new StandardEvaluationContext(new C()); + SpelExpressionParser parser = new SpelExpressionParser(); + String el1 = "as.![#this.equals('abc')]"; + SpelExpression exp = parser.parseRaw(el1); + Object[] value = (Object[])exp.getValue(ctx); + // value is array containing [true,false] + Assert.assertEquals(Boolean.class,value[0].getClass()); + TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx); + Assert.assertEquals(Boolean.class, evaluated.getElementTypeDescriptor().getType()); } - + @Test public void testProjectionTypeDescriptors_3() throws Exception { - StandardEvaluationContext ctx = new StandardEvaluationContext(new C()); - SpelExpressionParser parser = new SpelExpressionParser(); - String el1 = "ms.![key.equals('abc')]"; - SpelExpression exp = parser.parseRaw(el1); - List value = (List)exp.getValue(ctx); - // value is list containing [true,false] - Assert.assertEquals(Boolean.class,value.get(0).getClass()); - TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx); - Assert.assertEquals(null, evaluated.getElementTypeDescriptor()); + StandardEvaluationContext ctx = new StandardEvaluationContext(new C()); + SpelExpressionParser parser = new SpelExpressionParser(); + String el1 = "ms.![key.equals('abc')]"; + SpelExpression exp = parser.parseRaw(el1); + List value = (List)exp.getValue(ctx); + // value is list containing [true,false] + Assert.assertEquals(Boolean.class,value.get(0).getClass()); + TypeDescriptor evaluated = exp.getValueTypeDescriptor(ctx); + Assert.assertEquals(null, evaluated.getElementTypeDescriptor()); } - + static class C { public List ls; public String[] as; @@ -772,19 +757,19 @@ public class SpringEL300Tests extends ExpressionTestCase { ms.put("def","pqr"); } } - + static class D { public String a; - + private D(String s) { a=s; } - + public String toString() { return "D("+a+")"; } } - + @Test public void testGreaterThanWithNulls_SPR7840() throws Exception { List list = new ArrayList(); @@ -794,30 +779,31 @@ public class SpringEL300Tests extends ExpressionTestCase { list.add(new D("ccc")); list.add(new D(null)); list.add(new D("zzz")); - - StandardEvaluationContext ctx = new StandardEvaluationContext(list); - SpelExpressionParser parser = new SpelExpressionParser(); - - String el1 = "#root.?[a < 'hhh']"; - SpelExpression exp = parser.parseRaw(el1); - Object value = exp.getValue(ctx); - assertEquals("[D(aaa), D(bbb), D(null), D(ccc), D(null)]",value.toString()); - - String el2 = "#root.?[a > 'hhh']"; - SpelExpression exp2 = parser.parseRaw(el2); - Object value2 = exp2.getValue(ctx); - assertEquals("[D(zzz)]",value2.toString()); - - // trim out the nulls first - String el3 = "#root.?[a!=null].?[a < 'hhh']"; - SpelExpression exp3 = parser.parseRaw(el3); - Object value3 = exp3.getValue(ctx); - assertEquals("[D(aaa), D(bbb), D(ccc)]",value3.toString()); + + StandardEvaluationContext ctx = new StandardEvaluationContext(list); + SpelExpressionParser parser = new SpelExpressionParser(); + + String el1 = "#root.?[a < 'hhh']"; + SpelExpression exp = parser.parseRaw(el1); + Object value = exp.getValue(ctx); + assertEquals("[D(aaa), D(bbb), D(null), D(ccc), D(null)]",value.toString()); + + String el2 = "#root.?[a > 'hhh']"; + SpelExpression exp2 = parser.parseRaw(el2); + Object value2 = exp2.getValue(ctx); + assertEquals("[D(zzz)]",value2.toString()); + + // trim out the nulls first + String el3 = "#root.?[a!=null].?[a < 'hhh']"; + SpelExpression exp3 = parser.parseRaw(el3); + Object value3 = exp3.getValue(ctx); + assertEquals("[D(aaa), D(bbb), D(ccc)]",value3.toString()); } /** - * Test whether {@link ReflectiveMethodResolver} follows Java Method Invocation Conversion order. And more precisely - * that widening reference conversion is 'higher' than a unboxing conversion. + * Test whether {@link ReflectiveMethodResolver} follows Java Method Invocation + * Conversion order. And more precisely that widening reference conversion is 'higher' + * than a unboxing conversion. */ @Test public void testConversionPriority_8224() throws Exception { @@ -904,16 +890,16 @@ public class SpringEL300Tests extends ExpressionTestCase { } - + @Test public void varargsAndPrimitives_SPR8174() throws Exception { EvaluationContext emptyEvalContext = new StandardEvaluationContext(); List args = new ArrayList(); - + args.add(TypeDescriptor.forObject(34L)); ReflectionUtil ru = new ReflectionUtil(); MethodExecutor me = new ReflectiveMethodResolver().resolve(emptyEvalContext,ru,"methodToCall",args); - + args.set(0,TypeDescriptor.forObject(23)); me = new ReflectiveMethodResolver().resolve(emptyEvalContext,ru,"foo",args); me.execute(emptyEvalContext, ru, 45); @@ -941,127 +927,126 @@ public class SpringEL300Tests extends ExpressionTestCase { args.set(0,TypeDescriptor.forObject((byte)23)); me = new ReflectiveMethodResolver().resolve(emptyEvalContext,ru,"foo",args); me.execute(emptyEvalContext, ru, (byte)23); - + args.set(0,TypeDescriptor.forObject(true)); me = new ReflectiveMethodResolver().resolve(emptyEvalContext,ru,"foo",args); me.execute(emptyEvalContext, ru, true); - + // trickier: args.set(0,TypeDescriptor.forObject(12)); args.add(TypeDescriptor.forObject(23f)); me = new ReflectiveMethodResolver().resolve(emptyEvalContext,ru,"bar",args); me.execute(emptyEvalContext, ru, 12,23f); - + } - - - + + + public class ReflectionUtil { - public Object methodToCall(T param) { - System.out.println(param+" "+param.getClass()); - return "Object methodToCall(T param)"; - } - - public void foo(int... array) { - if (array.length==0) { - throw new RuntimeException(); - } - } - public void foo(float...array) { - if (array.length==0) { - throw new RuntimeException(); - } - } - public void foo(double...array) { - if (array.length==0) { - throw new RuntimeException(); - } - } - public void foo(short...array) { - if (array.length==0) { - throw new RuntimeException(); - } - } - public void foo(long...array) { - if (array.length==0) { - throw new RuntimeException(); - } - } - public void foo(boolean...array) { - if (array.length==0) { - throw new RuntimeException(); - } - } - public void foo(char...array) { - if (array.length==0) { - throw new RuntimeException(); - } - } - public void foo(byte...array) { - if (array.length==0) { - throw new RuntimeException(); - } - } - - public void bar(int... array) { - if (array.length==0) { - throw new RuntimeException(); - } - } - } - + public Object methodToCall(T param) { + System.out.println(param+" "+param.getClass()); + return "Object methodToCall(T param)"; + } + + public void foo(int... array) { + if (array.length==0) { + throw new RuntimeException(); + } + } + public void foo(float...array) { + if (array.length==0) { + throw new RuntimeException(); + } + } + public void foo(double...array) { + if (array.length==0) { + throw new RuntimeException(); + } + } + public void foo(short...array) { + if (array.length==0) { + throw new RuntimeException(); + } + } + public void foo(long...array) { + if (array.length==0) { + throw new RuntimeException(); + } + } + public void foo(boolean...array) { + if (array.length==0) { + throw new RuntimeException(); + } + } + public void foo(char...array) { + if (array.length==0) { + throw new RuntimeException(); + } + } + public void foo(byte...array) { + if (array.length==0) { + throw new RuntimeException(); + } + } + + public void bar(int... array) { + if (array.length==0) { + throw new RuntimeException(); + } + } + } + @Test public void testReservedWords_8228() throws Exception { // "DIV","EQ","GE","GT","LE","LT","MOD","NE","NOT" - @SuppressWarnings("unused") - class Reserver { - public Reserver getReserver() { - return this; - } - public String NE = "abc"; - public String ne = "def"; - - public int DIV = 1; - public int div = 3; - - public Map m = new HashMap(); - - @SuppressWarnings("unchecked") + @SuppressWarnings("unused") + class Reserver { + public Reserver getReserver() { + return this; + } + public String NE = "abc"; + public String ne = "def"; + + public int DIV = 1; + public int div = 3; + + public Map m = new HashMap(); + Reserver() { - m.put("NE","xyz"); - } - } - StandardEvaluationContext ctx = new StandardEvaluationContext(new Reserver()); - SpelExpressionParser parser = new SpelExpressionParser(); - String ex = "getReserver().NE"; - SpelExpression exp = null; - exp = parser.parseRaw(ex); - String value = (String)exp.getValue(ctx); - Assert.assertEquals("abc",value); - - ex = "getReserver().ne"; - exp = parser.parseRaw(ex); - value = (String)exp.getValue(ctx); - Assert.assertEquals("def",value); - - ex = "getReserver().m[NE]"; - exp = parser.parseRaw(ex); - value = (String)exp.getValue(ctx); - Assert.assertEquals("xyz",value); - - ex = "getReserver().DIV"; - exp = parser.parseRaw(ex); - Assert.assertEquals(1,exp.getValue(ctx)); - - ex = "getReserver().div"; - exp = parser.parseRaw(ex); - Assert.assertEquals(3,exp.getValue(ctx)); - - exp = parser.parseRaw("NE"); - Assert.assertEquals("abc",exp.getValue(ctx)); + m.put("NE","xyz"); + } + } + StandardEvaluationContext ctx = new StandardEvaluationContext(new Reserver()); + SpelExpressionParser parser = new SpelExpressionParser(); + String ex = "getReserver().NE"; + SpelExpression exp = null; + exp = parser.parseRaw(ex); + String value = (String)exp.getValue(ctx); + Assert.assertEquals("abc",value); + + ex = "getReserver().ne"; + exp = parser.parseRaw(ex); + value = (String)exp.getValue(ctx); + Assert.assertEquals("def",value); + + ex = "getReserver().m[NE]"; + exp = parser.parseRaw(ex); + value = (String)exp.getValue(ctx); + Assert.assertEquals("xyz",value); + + ex = "getReserver().DIV"; + exp = parser.parseRaw(ex); + Assert.assertEquals(1,exp.getValue(ctx)); + + ex = "getReserver().div"; + exp = parser.parseRaw(ex); + Assert.assertEquals(3,exp.getValue(ctx)); + + exp = parser.parseRaw("NE"); + Assert.assertEquals("abc",exp.getValue(ctx)); } - + /** * We add property accessors in the order: * First, Second, Third, Fourth. @@ -1071,39 +1056,24 @@ public class SpringEL300Tests extends ExpressionTestCase { @Test public void testPropertyAccessorOrder_8211() { ExpressionParser expressionParser = new SpelExpressionParser(); - StandardEvaluationContext evaluationContext = + StandardEvaluationContext evaluationContext = new StandardEvaluationContext(new ContextObject()); - + evaluationContext.addPropertyAccessor(new TestPropertyAccessor("firstContext")); evaluationContext.addPropertyAccessor(new TestPropertyAccessor("secondContext")); evaluationContext.addPropertyAccessor(new TestPropertyAccessor("thirdContext")); evaluationContext.addPropertyAccessor(new TestPropertyAccessor("fourthContext")); - - assertEquals("first", + + assertEquals("first", expressionParser.parseExpression("shouldBeFirst").getValue(evaluationContext)); - assertEquals("second", + assertEquals("second", expressionParser.parseExpression("shouldBeSecond").getValue(evaluationContext)); - assertEquals("third", + assertEquals("third", expressionParser.parseExpression("shouldBeThird").getValue(evaluationContext)); - assertEquals("fourth", + assertEquals("fourth", expressionParser.parseExpression("shouldBeFourth").getValue(evaluationContext)); } - - // test not quite complete, it doesn't check that the list of resolvers at the end of - // PropertyOrFieldReference.getPropertyAccessorsToTry() doesn't contain duplicates, which - // is what it is trying to test by having a property accessor that returns specific - // classes Integer and Number -// @Test -// public void testPropertyAccessorOrder_8211_2() { -// ExpressionParser expressionParser = new SpelExpressionParser(); -// StandardEvaluationContext evaluationContext = -// new StandardEvaluationContext(new Integer(42)); -// -// evaluationContext.addPropertyAccessor(new TestPropertyAccessor2()); -// -// assertEquals("42", expressionParser.parseExpression("x").getValue(evaluationContext)); -// } - + class TestPropertyAccessor implements PropertyAccessor { private String mapName; public TestPropertyAccessor(String mapName) { @@ -1139,38 +1109,13 @@ public class SpringEL300Tests extends ExpressionTestCase { } } - -// class TestPropertyAccessor2 implements PropertyAccessor { -// -// public Class[] getSpecificTargetClasses() { -// return new Class[]{Integer.class,Number.class}; -// } -// -// public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException { -// return true; -// } -// -// public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { -// return new TypedValue(target.toString(),TypeDescriptor.valueOf(String.class)); -// } -// -// public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { -// return false; -// } -// -// public void write(EvaluationContext context, Object target, String name, Object newValue) -// throws AccessException { -// throw new UnsupportedOperationException(); -// } -// -// } class ContextObject { public Map firstContext = new HashMap(); public Map secondContext = new HashMap(); public Map thirdContext = new HashMap(); public Map fourthContext = new HashMap(); - + public ContextObject() { firstContext.put("shouldBeFirst", "first"); secondContext.put("shouldBeFirst", "second"); @@ -1180,10 +1125,10 @@ public class SpringEL300Tests extends ExpressionTestCase { secondContext.put("shouldBeSecond", "second"); thirdContext.put("shouldBeSecond", "third"); fourthContext.put("shouldBeSecond", "fourth"); - + thirdContext.put("shouldBeThird", "third"); fourthContext.put("shouldBeThird", "fourth"); - + fourthContext.put("shouldBeFourth", "fourth"); } public Map getFirstContext() {return firstContext;} From 90bed9718f17bfd8f3d9dbe6cd2b3cf7ed2e6573 Mon Sep 17 00:00:00 2001 From: Andy Clement Date: Wed, 25 Jan 2012 17:32:15 -0800 Subject: [PATCH 2/2] Allow customization of SpEL method resolution This change introduces a protected ReflectiveMethodResolver#getMethods, allowing subclasses to specify additional static methods not declared directly on the type being evaluated. These methods then become candidates for filtering by any registered MethodFilters and ultimately become available within for use within SpEL expressions. Issue: SPR-9038 --- .../support/ReflectiveMethodResolver.java | 16 +++++++- .../expression/spel/SpringEL300Tests.java | 38 +++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java index 019faf2836b..33d1f510b17 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -90,7 +90,7 @@ public class ReflectiveMethodResolver implements MethodResolver { try { TypeConverter typeConverter = context.getTypeConverter(); Class type = (targetObject instanceof Class ? (Class) targetObject : targetObject.getClass()); - Method[] methods = type.getMethods(); + Method[] methods = getMethods(type); // If a filter is registered for this type, call it MethodFilter filter = (this.filters != null ? this.filters.get(type) : null); @@ -197,4 +197,16 @@ public class ReflectiveMethodResolver implements MethodResolver { } } + /** + * Return the set of methods for this type. The default implementation returns the + * result of Class#getMethods for the given {@code type}, but subclasses may override + * in order to alter the results, e.g. specifying static methods declared elsewhere. + * + * @param type the class for which to return the methods + * @since 3.1.1 + */ + protected Method[] getMethods(Class type) { + return type.getMethods(); + } + } diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java index e28f9dd3a15..0fa4d1fbd1d 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpringEL300Tests.java @@ -17,8 +17,10 @@ package org.springframework.expression.spel; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; @@ -38,6 +40,7 @@ import org.springframework.expression.EvaluationException; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.MethodExecutor; +import org.springframework.expression.MethodResolver; import org.springframework.expression.ParserContext; import org.springframework.expression.PropertyAccessor; import org.springframework.expression.TypedValue; @@ -1137,6 +1140,41 @@ public class SpringEL300Tests extends ExpressionTestCase { public Map getFourthContext() {return fourthContext;} } + /** + * Test the ability to subclass the ReflectiveMethodResolver and change how it + * determines the set of methods for a type. + */ + @Test + public void testCustomStaticFunctions_SPR9038() { + try { + ExpressionParser parser = new SpelExpressionParser(); + StandardEvaluationContext context = new StandardEvaluationContext(); + List methodResolvers = new ArrayList(); + methodResolvers.add(new ReflectiveMethodResolver() { + @Override + protected Method[] getMethods(Class type) { + try { + return new Method[] { + Integer.class.getDeclaredMethod("parseInt", new Class[] { + String.class, Integer.TYPE }) }; + } catch (NoSuchMethodException e1) { + return new Method[0]; + } + } + }); + + context.setMethodResolvers(methodResolvers); + org.springframework.expression.Expression expression = + parser.parseExpression("parseInt('-FF', 16)"); + + Integer result = expression.getValue(context, "", Integer.class); + assertEquals("Equal assertion failed: ", -255, result.intValue()); + } catch (Exception e) { + e.printStackTrace(); + fail("Unexpected exception: "+e.toString()); + } + } + }