/* * Copyright 2002-2016 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.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.junit.Before; import org.junit.Test; import org.springframework.core.MethodParameter; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.expression.EvaluationException; import org.springframework.expression.Expression; import org.springframework.expression.TypeConverter; import org.springframework.expression.spel.support.StandardEvaluationContext; import static org.junit.Assert.*; /** * Expression evaluation where the TypeConverter plugged in is the * {@link org.springframework.core.convert.support.GenericConversionService}. * * @author Andy Clement * @author Dave Syer */ public class ExpressionWithConversionTests extends AbstractExpressionTests { private static List<String> listOfString = new ArrayList<>(); private static TypeDescriptor typeDescriptorForListOfString = null; private static List<Integer> listOfInteger = new ArrayList<>(); private static TypeDescriptor typeDescriptorForListOfInteger = null; static { listOfString.add("1"); listOfString.add("2"); listOfString.add("3"); listOfInteger.add(4); listOfInteger.add(5); listOfInteger.add(6); } @Before public void setUp() throws Exception { ExpressionWithConversionTests.typeDescriptorForListOfString = new TypeDescriptor(ExpressionWithConversionTests.class.getDeclaredField("listOfString")); ExpressionWithConversionTests.typeDescriptorForListOfInteger = new TypeDescriptor(ExpressionWithConversionTests.class.getDeclaredField("listOfInteger")); } /** * Test the service can convert what we are about to use in the expression evaluation tests. */ @Test public void testConversionsAvailable() throws Exception { TypeConvertorUsingConversionService tcs = new TypeConvertorUsingConversionService(); // ArrayList containing List<Integer> to List<String> Class<?> clazz = typeDescriptorForListOfString.getElementTypeDescriptor().getType(); assertEquals(String.class,clazz); List<?> l = (List<?>) tcs.convertValue(listOfInteger, TypeDescriptor.forObject(listOfInteger), typeDescriptorForListOfString); assertNotNull(l); // ArrayList containing List<String> to List<Integer> clazz = typeDescriptorForListOfInteger.getElementTypeDescriptor().getType(); assertEquals(Integer.class,clazz); l = (List<?>) tcs.convertValue(listOfString, TypeDescriptor.forObject(listOfString), typeDescriptorForListOfString); assertNotNull(l); } @Test public void testSetParameterizedList() throws Exception { StandardEvaluationContext context = TestScenarioCreator.getTestEvaluationContext(); Expression e = parser.parseExpression("listOfInteger.size()"); assertEquals(0,e.getValue(context,Integer.class).intValue()); context.setTypeConverter(new TypeConvertorUsingConversionService()); // Assign a List<String> to the List<Integer> field - the component elements should be converted parser.parseExpression("listOfInteger").setValue(context,listOfString); assertEquals(3,e.getValue(context,Integer.class).intValue()); // size now 3 Class<?> clazz = parser.parseExpression("listOfInteger[1].getClass()").getValue(context, Class.class); // element type correctly Integer assertEquals(Integer.class,clazz); } @Test public void testCoercionToCollectionOfPrimitive() throws Exception { class TestTarget { @SuppressWarnings("unused") public int sum(Collection<Integer> numbers) { int total = 0; for (int i : numbers) { total += i; } return total; } } StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); TypeDescriptor collectionType = new TypeDescriptor(new MethodParameter(TestTarget.class.getDeclaredMethod( "sum", Collection.class), 0)); // The type conversion is possible assertTrue(evaluationContext.getTypeConverter() .canConvert(TypeDescriptor.valueOf(String.class), collectionType)); // ... and it can be done successfully assertEquals("[1, 2, 3, 4]", evaluationContext.getTypeConverter().convertValue("1,2,3,4", TypeDescriptor.valueOf(String.class), collectionType).toString()); evaluationContext.setVariable("target", new TestTarget()); // OK up to here, so the evaluation should be fine... // ... but this fails int result = (Integer) parser.parseExpression("#target.sum(#root)").getValue(evaluationContext, "1,2,3,4"); assertEquals("Wrong result: " + result, 10, result); } @Test public void testConvert() { Foo root = new Foo("bar"); Collection<String> foos = Collections.singletonList("baz"); StandardEvaluationContext context = new StandardEvaluationContext(root); // property access Expression expression = parser.parseExpression("foos"); expression.setValue(context, foos); Foo baz = root.getFoos().iterator().next(); assertEquals("baz", baz.value); // method call expression = parser.parseExpression("setFoos(#foos)"); context.setVariable("foos", foos); expression.getValue(context); baz = root.getFoos().iterator().next(); assertEquals("baz", baz.value); // method call with result from method call expression = parser.parseExpression("setFoos(getFoosAsStrings())"); expression.getValue(context); baz = root.getFoos().iterator().next(); assertEquals("baz", baz.value); // method call with result from method call expression = parser.parseExpression("setFoos(getFoosAsObjects())"); expression.getValue(context); baz = root.getFoos().iterator().next(); assertEquals("baz", baz.value); } /** * Type converter that uses the core conversion service. */ private static class TypeConvertorUsingConversionService implements TypeConverter { private final ConversionService service = new DefaultConversionService(); @Override public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) { return this.service.canConvert(sourceType, targetType); } @Override public Object convertValue(Object value, TypeDescriptor sourceType, TypeDescriptor targetType) throws EvaluationException { return this.service.convert(value, sourceType, targetType); } } public static class Foo { public final String value; private Collection<Foo> foos; public Foo(String value) { this.value = value; } public void setFoos(Collection<Foo> foos) { this.foos = foos; } public Collection<Foo> getFoos() { return this.foos; } public Collection<String> getFoosAsStrings() { return Collections.singletonList("baz"); } public Collection<?> getFoosAsObjects() { return Collections.singletonList("baz"); } } }