/*
* 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.List;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.ConstructorExecutor;
import org.springframework.expression.ConstructorResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.testresources.PlaceOfBirth;
import static org.junit.Assert.*;
/**
* Tests invocation of constructors.
*
* @author Andy Clement
*/
public class ConstructorInvocationTests extends AbstractExpressionTests {
@Test
public void testTypeConstructors() {
evaluate("new String('hello world')", "hello world", String.class);
}
@Test
public void testNonExistentType() {
evaluateAndCheckError("new FooBar()", SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM);
}
@SuppressWarnings("serial")
static class TestException extends Exception {
}
static class Tester {
public static int counter;
public int i;
public Tester() {
}
public Tester(int i) throws Exception {
counter++;
if (i == 1) {
throw new IllegalArgumentException("IllegalArgumentException for 1");
}
if (i == 2) {
throw new RuntimeException("RuntimeException for 2");
}
if (i == 4) {
throw new TestException();
}
this.i = i;
}
public Tester(PlaceOfBirth pob) {
}
}
@Test
public void testConstructorThrowingException_SPR6760() {
// Test ctor on inventor:
// On 1 it will throw an IllegalArgumentException
// On 2 it will throw a RuntimeException
// On 3 it will exit normally
// In each case it increments the Tester field 'counter' when invoked
SpelExpressionParser parser = new SpelExpressionParser();
Expression expr = parser.parseExpression("new org.springframework.expression.spel.ConstructorInvocationTests$Tester(#bar).i");
// Normal exit
StandardEvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext();
eContext.setRootObject(new Tester());
eContext.setVariable("bar", 3);
Object o = expr.getValue(eContext);
assertEquals(o, 3);
assertEquals(1, parser.parseExpression("counter").getValue(eContext));
// Now the expression has cached that throwException(int) is the right thing to
// call. Let's change 'bar' to be a PlaceOfBirth which indicates the cached
// reference is out of date.
eContext.setVariable("bar", new PlaceOfBirth("London"));
o = expr.getValue(eContext);
assertEquals(0, o);
// That confirms the logic to mark the cached reference stale and retry is working
// Now let's cause the method to exit via exception and ensure it doesn't cause
// a retry.
// First, switch back to throwException(int)
eContext.setVariable("bar", 3);
o = expr.getValue(eContext);
assertEquals(3, o);
assertEquals(2, parser.parseExpression("counter").getValue(eContext));
// 4 will make it throw a checked exception - this will be wrapped by spel on the
// way out
eContext.setVariable("bar", 4);
try {
o = expr.getValue(eContext);
fail("Should have failed");
}
catch (Exception e) {
// A problem occurred whilst attempting to construct an object of type
// 'org.springframework.expression.spel.ConstructorInvocationTests$Tester'
// using arguments '(java.lang.Integer)'
int idx = e.getMessage().indexOf("Tester");
if (idx == -1) {
fail("Expected reference to Tester in :" + e.getMessage());
}
// normal
}
// If counter is 4 then the method got called twice!
assertEquals(3, parser.parseExpression("counter").getValue(eContext));
// 1 will make it throw a RuntimeException - SpEL will let this through
eContext.setVariable("bar", 1);
try {
o = expr.getValue(eContext);
fail("Should have failed");
}
catch (Exception e) {
// A problem occurred whilst attempting to construct an object of type
// 'org.springframework.expression.spel.ConstructorInvocationTests$Tester'
// using arguments '(java.lang.Integer)'
if (e instanceof SpelEvaluationException) {
e.printStackTrace();
fail("Should not have been wrapped");
}
}
// If counter is 5 then the method got called twice!
assertEquals(4, parser.parseExpression("counter").getValue(eContext));
}
@Test
public void testAddingConstructorResolvers() {
StandardEvaluationContext ctx = new StandardEvaluationContext();
// reflective constructor accessor is the only one by default
List<ConstructorResolver> constructorResolvers = ctx.getConstructorResolvers();
assertEquals(1, constructorResolvers.size());
ConstructorResolver dummy = new DummyConstructorResolver();
ctx.addConstructorResolver(dummy);
assertEquals(2, ctx.getConstructorResolvers().size());
List<ConstructorResolver> copy = new ArrayList<>();
copy.addAll(ctx.getConstructorResolvers());
assertTrue(ctx.removeConstructorResolver(dummy));
assertFalse(ctx.removeConstructorResolver(dummy));
assertEquals(1, ctx.getConstructorResolvers().size());
ctx.setConstructorResolvers(copy);
assertEquals(2, ctx.getConstructorResolvers().size());
}
static class DummyConstructorResolver implements ConstructorResolver {
@Override
public ConstructorExecutor resolve(EvaluationContext context, String typeName,
List<TypeDescriptor> argumentTypes) throws AccessException {
throw new UnsupportedOperationException("Auto-generated method stub");
}
}
@Test
public void testVarargsInvocation01() {
// Calling 'Fruit(String... strings)'
evaluate("new org.springframework.expression.spel.testresources.Fruit('a','b','c').stringscount()", 3,
Integer.class);
evaluate("new org.springframework.expression.spel.testresources.Fruit('a').stringscount()", 1, Integer.class);
evaluate("new org.springframework.expression.spel.testresources.Fruit().stringscount()", 0, Integer.class);
// all need converting to strings
evaluate("new org.springframework.expression.spel.testresources.Fruit(1,2,3).stringscount()", 3, Integer.class);
// needs string conversion
evaluate("new org.springframework.expression.spel.testresources.Fruit(1).stringscount()", 1, Integer.class);
// first and last need conversion
evaluate("new org.springframework.expression.spel.testresources.Fruit(1,'a',3.0d).stringscount()", 3,
Integer.class);
}
@Test
public void testVarargsInvocation02() {
// Calling 'Fruit(int i, String... strings)' - returns int+length_of_strings
evaluate("new org.springframework.expression.spel.testresources.Fruit(5,'a','b','c').stringscount()", 8,
Integer.class);
evaluate("new org.springframework.expression.spel.testresources.Fruit(2,'a').stringscount()", 3, Integer.class);
evaluate("new org.springframework.expression.spel.testresources.Fruit(4).stringscount()", 4, Integer.class);
evaluate("new org.springframework.expression.spel.testresources.Fruit(8,2,3).stringscount()", 10, Integer.class);
evaluate("new org.springframework.expression.spel.testresources.Fruit(9).stringscount()", 9, Integer.class);
evaluate("new org.springframework.expression.spel.testresources.Fruit(2,'a',3.0d).stringscount()", 4,
Integer.class);
evaluate(
"new org.springframework.expression.spel.testresources.Fruit(8,stringArrayOfThreeItems).stringscount()",
11, Integer.class);
}
/*
* These tests are attempting to call constructors where we need to widen or convert
* the argument in order to satisfy a suitable constructor.
*/
@Test
public void testWidening01() {
// widening of int 3 to double 3 is OK
evaluate("new Double(3)", 3.0d, Double.class);
// widening of int 3 to long 3 is OK
evaluate("new Long(3)", 3L, Long.class);
}
@Test
@Ignore
public void testArgumentConversion01() {
// Closest ctor will be new String(String) and converter supports Double>String
// TODO currently failing as with new ObjectToArray converter closest constructor
// matched becomes String(byte[]) which fails...
evaluate("new String(3.0d)", "3.0", String.class);
}
}