MethodParameter supports Java 8 Executable/Parameter and validates parameter indexes

Also, equals insists on the same class now, differentiating from SynthesizingMethodParameter.

Issue: SPR-14055
Issue: SPR-13456
Issue: SPR-14438
This commit is contained in:
Juergen Hoeller 2016-07-07 00:37:52 +02:00
parent da9c24c41e
commit 39e3f2ebf6
9 changed files with 313 additions and 87 deletions

View File

@ -18,7 +18,7 @@ package org.springframework.beans.factory.support;
import java.beans.ConstructorProperties; import java.beans.ConstructorProperties;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Member; import java.lang.reflect.Executable;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.security.AccessController; import java.security.AccessController;
@ -506,7 +506,7 @@ class ConstructorResolver {
// and explicitly ignore overridden methods (with the same parameter signature). // and explicitly ignore overridden methods (with the same parameter signature).
else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight && else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight &&
!mbd.isLenientConstructorResolution() && !mbd.isLenientConstructorResolution() &&
paramTypes.length == factoryMethodToUse.getParameterTypes().length && paramTypes.length == factoryMethodToUse.getParameterCount() &&
!Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes())) { !Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes())) {
if (ambiguousFactoryMethods == null) { if (ambiguousFactoryMethods == null) {
ambiguousFactoryMethods = new LinkedHashSet<>(); ambiguousFactoryMethods = new LinkedHashSet<>();
@ -662,7 +662,7 @@ class ConstructorResolver {
*/ */
private ArgumentsHolder createArgumentArray( private ArgumentsHolder createArgumentArray(
String beanName, RootBeanDefinition mbd, ConstructorArgumentValues resolvedValues, String beanName, RootBeanDefinition mbd, ConstructorArgumentValues resolvedValues,
BeanWrapper bw, Class<?>[] paramTypes, String[] paramNames, Object methodOrCtor, BeanWrapper bw, Class<?>[] paramTypes, String[] paramNames, Executable executable,
boolean autowiring) throws UnsatisfiedDependencyException { boolean autowiring) throws UnsatisfiedDependencyException {
TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ? TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ?
@ -699,7 +699,7 @@ class ConstructorResolver {
ConstructorArgumentValues.ValueHolder sourceHolder = ConstructorArgumentValues.ValueHolder sourceHolder =
(ConstructorArgumentValues.ValueHolder) valueHolder.getSource(); (ConstructorArgumentValues.ValueHolder) valueHolder.getSource();
Object sourceValue = sourceHolder.getValue(); Object sourceValue = sourceHolder.getValue();
MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex); MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex);
try { try {
convertedValue = converter.convertIfNecessary(originalValue, paramType, methodParam); convertedValue = converter.convertIfNecessary(originalValue, paramType, methodParam);
// TODO re-enable once race condition has been found (SPR-7423) // TODO re-enable once race condition has been found (SPR-7423)
@ -727,7 +727,7 @@ class ConstructorResolver {
args.rawArguments[paramIndex] = originalValue; args.rawArguments[paramIndex] = originalValue;
} }
else { else {
MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex); MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex);
// No explicit match found: we're either supposed to autowire or // No explicit match found: we're either supposed to autowire or
// have to fail creating an argument array for the given constructor. // have to fail creating an argument array for the given constructor.
if (!autowiring) { if (!autowiring) {
@ -755,7 +755,7 @@ class ConstructorResolver {
this.beanFactory.registerDependentBean(autowiredBeanName, beanName); this.beanFactory.registerDependentBean(autowiredBeanName, beanName);
if (this.beanFactory.logger.isDebugEnabled()) { if (this.beanFactory.logger.isDebugEnabled()) {
this.beanFactory.logger.debug("Autowiring by type from bean name '" + beanName + this.beanFactory.logger.debug("Autowiring by type from bean name '" + beanName +
"' via " + (methodOrCtor instanceof Constructor ? "constructor" : "factory method") + "' via " + (executable instanceof Constructor ? "constructor" : "factory method") +
" to bean named '" + autowiredBeanName + "'"); " to bean named '" + autowiredBeanName + "'");
} }
} }
@ -767,10 +767,9 @@ class ConstructorResolver {
* Resolve the prepared arguments stored in the given bean definition. * Resolve the prepared arguments stored in the given bean definition.
*/ */
private Object[] resolvePreparedArguments( private Object[] resolvePreparedArguments(
String beanName, RootBeanDefinition mbd, BeanWrapper bw, Member methodOrCtor, Object[] argsToResolve) { String beanName, RootBeanDefinition mbd, BeanWrapper bw, Executable executable, Object[] argsToResolve) {
Class<?>[] paramTypes = (methodOrCtor instanceof Method ? Class<?>[] paramTypes = executable.getParameterTypes();
((Method) methodOrCtor).getParameterTypes() : ((Constructor<?>) methodOrCtor).getParameterTypes());
TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ? TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ?
this.beanFactory.getCustomTypeConverter() : bw); this.beanFactory.getCustomTypeConverter() : bw);
BeanDefinitionValueResolver valueResolver = BeanDefinitionValueResolver valueResolver =
@ -778,8 +777,8 @@ class ConstructorResolver {
Object[] resolvedArgs = new Object[argsToResolve.length]; Object[] resolvedArgs = new Object[argsToResolve.length];
for (int argIndex = 0; argIndex < argsToResolve.length; argIndex++) { for (int argIndex = 0; argIndex < argsToResolve.length; argIndex++) {
Object argValue = argsToResolve[argIndex]; Object argValue = argsToResolve[argIndex];
MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, argIndex); MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex);
GenericTypeResolver.resolveParameterType(methodParam, methodOrCtor.getDeclaringClass()); GenericTypeResolver.resolveParameterType(methodParam, executable.getDeclaringClass());
if (argValue instanceof AutowiredArgumentMarker) { if (argValue instanceof AutowiredArgumentMarker) {
argValue = resolveAutowiredArgument(methodParam, beanName, null, converter); argValue = resolveAutowiredArgument(methodParam, beanName, null, converter);
} }

View File

@ -19,8 +19,10 @@ package org.springframework.core;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Member; import java.lang.reflect.Member;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.HashMap; import java.util.HashMap;
@ -54,6 +56,8 @@ public class MethodParameter {
private final int parameterIndex; private final int parameterIndex;
private volatile Parameter parameter;
private int nestingLevel = 1; private int nestingLevel = 1;
/** Map from Integer level to Integer type index */ /** Map from Integer level to Integer type index */
@ -98,7 +102,7 @@ public class MethodParameter {
public MethodParameter(Method method, int parameterIndex, int nestingLevel) { public MethodParameter(Method method, int parameterIndex, int nestingLevel) {
Assert.notNull(method, "Method must not be null"); Assert.notNull(method, "Method must not be null");
this.method = method; this.method = method;
this.parameterIndex = parameterIndex; this.parameterIndex = validateIndex(method, parameterIndex);
this.nestingLevel = nestingLevel; this.nestingLevel = nestingLevel;
this.constructor = null; this.constructor = null;
} }
@ -123,7 +127,7 @@ public class MethodParameter {
public MethodParameter(Constructor<?> constructor, int parameterIndex, int nestingLevel) { public MethodParameter(Constructor<?> constructor, int parameterIndex, int nestingLevel) {
Assert.notNull(constructor, "Constructor must not be null"); Assert.notNull(constructor, "Constructor must not be null");
this.constructor = constructor; this.constructor = constructor;
this.parameterIndex = parameterIndex; this.parameterIndex = validateIndex(constructor, parameterIndex);
this.nestingLevel = nestingLevel; this.nestingLevel = nestingLevel;
this.method = null; this.method = null;
} }
@ -138,6 +142,7 @@ public class MethodParameter {
this.method = original.method; this.method = original.method;
this.constructor = original.constructor; this.constructor = original.constructor;
this.parameterIndex = original.parameterIndex; this.parameterIndex = original.parameterIndex;
this.parameter = original.parameter;
this.nestingLevel = original.nestingLevel; this.nestingLevel = original.nestingLevel;
this.typeIndexesPerLevel = original.typeIndexesPerLevel; this.typeIndexesPerLevel = original.typeIndexesPerLevel;
this.containingClass = original.containingClass; this.containingClass = original.containingClass;
@ -179,15 +184,7 @@ public class MethodParameter {
* @return the Method or Constructor as Member * @return the Method or Constructor as Member
*/ */
public Member getMember() { public Member getMember() {
// NOTE: no ternary expression to retain JDK <8 compatibility even when using return getExecutable();
// the JDK 8 compiler (potentially selecting java.lang.reflect.Executable
// as common type, with that new base class not available on older JDKs)
if (this.method != null) {
return this.method;
}
else {
return this.constructor;
}
} }
/** /**
@ -197,15 +194,27 @@ public class MethodParameter {
* @return the Method or Constructor as AnnotatedElement * @return the Method or Constructor as AnnotatedElement
*/ */
public AnnotatedElement getAnnotatedElement() { public AnnotatedElement getAnnotatedElement() {
// NOTE: no ternary expression to retain JDK <8 compatibility even when using return getExecutable();
// the JDK 8 compiler (potentially selecting java.lang.reflect.Executable }
// as common type, with that new base class not available on older JDKs)
if (this.method != null) { /**
return this.method; * Return the wrapped executable.
} * @return the Method or Constructor as Executable
else { * @since 5.0
return this.constructor; */
public Executable getExecutable() {
return (this.method != null ? this.method : this.constructor);
}
/**
* Return the {@link Parameter} descriptor for method/constructor parameter.
* @since 5.0
*/
public Parameter getParameter() {
if (this.parameter == null) {
this.parameter = getExecutable().getParameters()[this.parameterIndex];
} }
return this.parameter;
} }
/** /**
@ -570,11 +579,11 @@ public class MethodParameter {
if (this == other) { if (this == other) {
return true; return true;
} }
if (!(other instanceof MethodParameter)) { if (other == null || getClass() != other.getClass()) {
return false; return false;
} }
MethodParameter otherParam = (MethodParameter) other; MethodParameter otherParam = (MethodParameter) other;
return (this.parameterIndex == otherParam.parameterIndex && getMember().equals(otherParam.getMember())); return (this.parameterIndex == otherParam.parameterIndex && getExecutable().equals(otherParam.getExecutable()));
} }
@Override @Override
@ -596,23 +605,73 @@ public class MethodParameter {
/** /**
* Create a new MethodParameter for the given method or constructor. * Create a new MethodParameter for the given method or constructor.
* <p>This is a convenience constructor for scenarios where a * <p>This is a convenience factory method for scenarios where a
* Method or Constructor reference is treated in a generic fashion. * Method or Constructor reference is treated in a generic fashion.
* @param methodOrConstructor the Method or Constructor to specify a parameter for * @param methodOrConstructor the Method or Constructor to specify a parameter for
* @param parameterIndex the index of the parameter * @param parameterIndex the index of the parameter
* @return the corresponding MethodParameter instance * @return the corresponding MethodParameter instance
* @deprecated as of 5.0, in favor of {@link #forExecutable}
*/ */
@Deprecated
public static MethodParameter forMethodOrConstructor(Object methodOrConstructor, int parameterIndex) { public static MethodParameter forMethodOrConstructor(Object methodOrConstructor, int parameterIndex) {
if (methodOrConstructor instanceof Method) { if (!(methodOrConstructor instanceof Executable)) {
return new MethodParameter((Method) methodOrConstructor, parameterIndex);
}
else if (methodOrConstructor instanceof Constructor) {
return new MethodParameter((Constructor<?>) methodOrConstructor, parameterIndex);
}
else {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Given object [" + methodOrConstructor + "] is neither a Method nor a Constructor"); "Given object [" + methodOrConstructor + "] is neither a Method nor a Constructor");
} }
return forExecutable((Executable) methodOrConstructor, parameterIndex);
}
/**
* Create a new MethodParameter for the given method or constructor.
* <p>This is a convenience factory method for scenarios where a
* Method or Constructor reference is treated in a generic fashion.
* @param executable the Method or Constructor to specify a parameter for
* @param parameterIndex the index of the parameter
* @return the corresponding MethodParameter instance
* @since 5.0
*/
public static MethodParameter forExecutable(Executable executable, int parameterIndex) {
if (executable instanceof Method) {
return new MethodParameter((Method) executable, parameterIndex);
}
else if (executable instanceof Constructor) {
return new MethodParameter((Constructor<?>) executable, parameterIndex);
}
else {
throw new IllegalArgumentException("Not a Method/Constructor: " + executable);
}
}
/**
* Create a new MethodParameter for the given parameter descriptor.
* <p>This is a convenience factory method for scenarios where a
* Java 8 {@link Parameter} descriptor is already available.
* @param parameter the parameter descriptor
* @return the corresponding MethodParameter instance
* @since 5.0
*/
public static MethodParameter forParameter(Parameter parameter) {
return forExecutable(parameter.getDeclaringExecutable(), findParameterIndex(parameter));
}
protected static int findParameterIndex(Parameter parameter) {
Executable executable = parameter.getDeclaringExecutable();
Parameter[] allParams = executable.getParameters();
for (int i = 0; i < allParams.length; i++) {
if (parameter == allParams[i]) {
return i;
}
}
throw new IllegalArgumentException("Given parameter [" + parameter +
"] does not match any parameter in the declaring executable");
}
private static int validateIndex(Executable executable, int parameterIndex) {
int count = executable.getParameterCount();
if (parameterIndex >= count) {
throw new IllegalArgumentException("Parameter index needs to be between 0 and " + (count - 1));
}
return parameterIndex;
} }
} }

View File

@ -18,7 +18,9 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
@ -108,4 +110,38 @@ public class SynthesizingMethodParameter extends MethodParameter {
return new SynthesizingMethodParameter(this); return new SynthesizingMethodParameter(this);
} }
/**
* Create a new SynthesizingMethodParameter for the given method or constructor.
* <p>This is a convenience factory method for scenarios where a
* Method or Constructor reference is treated in a generic fashion.
* @param executable the Method or Constructor to specify a parameter for
* @param parameterIndex the index of the parameter
* @return the corresponding SynthesizingMethodParameter instance
* @since 5.0
*/
public static SynthesizingMethodParameter forExecutable(Executable executable, int parameterIndex) {
if (executable instanceof Method) {
return new SynthesizingMethodParameter((Method) executable, parameterIndex);
}
else if (executable instanceof Constructor) {
return new SynthesizingMethodParameter((Constructor<?>) executable, parameterIndex);
}
else {
throw new IllegalArgumentException("Not a Method/Constructor: " + executable);
}
}
/**
* Create a new SynthesizingMethodParameter for the given parameter descriptor.
* <p>This is a convenience factory method for scenarios where a
* Java 8 {@link Parameter} descriptor is already available.
* @param parameter the parameter descriptor
* @return the corresponding SynthesizingMethodParameter instance
* @since 5.0
*/
public static SynthesizingMethodParameter forParameter(Parameter parameter) {
return forExecutable(parameter.getDeclaringExecutable(), findParameterIndex(parameter));
}
} }

View File

@ -158,7 +158,7 @@ public class GenericTypeResolverTests {
@Test @Test
public void getGenericsOnArrayFromParamCannotBeResolved() throws Exception { public void getGenericsOnArrayFromParamCannotBeResolved() throws Exception {
// SPR-11044 // SPR-11044
MethodParameter methodParameter = MethodParameter.forMethodOrConstructor( MethodParameter methodParameter = MethodParameter.forExecutable(
WithArrayBase.class.getDeclaredMethod("array", Object[].class), 0); WithArrayBase.class.getDeclaredMethod("array", Object[].class), 0);
Class<?> resolved = GenericTypeResolver.resolveParameterType(methodParameter, WithArray.class); Class<?> resolved = GenericTypeResolver.resolveParameterType(methodParameter, WithArray.class);
assertThat(resolved, equalTo((Class<?>) Object[].class)); assertThat(resolved, equalTo((Class<?>) Object[].class));

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -25,9 +25,12 @@ import static org.junit.Assert.*;
/** /**
* @author Arjen Poutsma * @author Arjen Poutsma
* @author Juergen Hoeller
*/ */
public class MethodParameterTests { public class MethodParameterTests {
private Method method;
private MethodParameter stringParameter; private MethodParameter stringParameter;
private MethodParameter longParameter; private MethodParameter longParameter;
@ -37,12 +40,13 @@ public class MethodParameterTests {
@Before @Before
public void setUp() throws NoSuchMethodException { public void setUp() throws NoSuchMethodException {
Method method = getClass().getMethod("method", String.class, Long.TYPE); method = getClass().getMethod("method", String.class, Long.TYPE);
stringParameter = new MethodParameter(method, 0); stringParameter = new MethodParameter(method, 0);
longParameter = new MethodParameter(method, 1); longParameter = new MethodParameter(method, 1);
intReturnType = new MethodParameter(method, -1); intReturnType = new MethodParameter(method, -1);
} }
@Test @Test
public void testEquals() throws NoSuchMethodException { public void testEquals() throws NoSuchMethodException {
assertEquals(stringParameter, stringParameter); assertEquals(stringParameter, stringParameter);
@ -60,8 +64,8 @@ public class MethodParameterTests {
MethodParameter methodParameter = new MethodParameter(method, 0); MethodParameter methodParameter = new MethodParameter(method, 0);
assertEquals(stringParameter, methodParameter); assertEquals(stringParameter, methodParameter);
assertEquals(methodParameter, stringParameter); assertEquals(methodParameter, stringParameter);
assertFalse(longParameter.equals(methodParameter)); assertNotEquals(longParameter, methodParameter);
assertFalse(methodParameter.equals(longParameter)); assertNotEquals(methodParameter, longParameter);
} }
@Test @Test
@ -73,7 +77,25 @@ public class MethodParameterTests {
Method method = getClass().getMethod("method", String.class, Long.TYPE); Method method = getClass().getMethod("method", String.class, Long.TYPE);
MethodParameter methodParameter = new MethodParameter(method, 0); MethodParameter methodParameter = new MethodParameter(method, 0);
assertEquals(stringParameter.hashCode(), methodParameter.hashCode()); assertEquals(stringParameter.hashCode(), methodParameter.hashCode());
assertTrue(longParameter.hashCode() != methodParameter.hashCode()); assertNotEquals(longParameter.hashCode(), methodParameter.hashCode());
}
@Test
@SuppressWarnings("deprecation")
public void testFactoryMethods() {
assertEquals(stringParameter, MethodParameter.forMethodOrConstructor(method, 0));
assertEquals(longParameter, MethodParameter.forMethodOrConstructor(method, 1));
assertEquals(stringParameter, MethodParameter.forExecutable(method, 0));
assertEquals(longParameter, MethodParameter.forExecutable(method, 1));
assertEquals(stringParameter, MethodParameter.forParameter(method.getParameters()[0]));
assertEquals(longParameter, MethodParameter.forParameter(method.getParameters()[1]));
}
@Test(expected = IllegalArgumentException.class)
public void testIndexValidation() {
new MethodParameter(method, 2);
} }

View File

@ -214,7 +214,7 @@ public class ResolvableTypeTests {
@Test @Test
public void forMethodParameter() throws Exception { public void forMethodParameter() throws Exception {
Method method = Methods.class.getMethod("charSequenceParameter", List.class); Method method = Methods.class.getMethod("charSequenceParameter", List.class);
MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0); MethodParameter methodParameter = MethodParameter.forExecutable(method, 0);
ResolvableType type = ResolvableType.forMethodParameter(methodParameter); ResolvableType type = ResolvableType.forMethodParameter(methodParameter);
assertThat(type.getType(), equalTo(method.getGenericParameterTypes()[0])); assertThat(type.getType(), equalTo(method.getGenericParameterTypes()[0]));
} }
@ -222,7 +222,7 @@ public class ResolvableTypeTests {
@Test @Test
public void forMethodParameterWithNesting() throws Exception { public void forMethodParameterWithNesting() throws Exception {
Method method = Methods.class.getMethod("nested", Map.class); Method method = Methods.class.getMethod("nested", Map.class);
MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0); MethodParameter methodParameter = MethodParameter.forExecutable(method, 0);
methodParameter.increaseNestingLevel(); methodParameter.increaseNestingLevel();
ResolvableType type = ResolvableType.forMethodParameter(methodParameter); ResolvableType type = ResolvableType.forMethodParameter(methodParameter);
assertThat(type.resolve(), equalTo((Class) Map.class)); assertThat(type.resolve(), equalTo((Class) Map.class));
@ -233,7 +233,7 @@ public class ResolvableTypeTests {
@Test @Test
public void forMethodParameterWithNestingAndLevels() throws Exception { public void forMethodParameterWithNestingAndLevels() throws Exception {
Method method = Methods.class.getMethod("nested", Map.class); Method method = Methods.class.getMethod("nested", Map.class);
MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0); MethodParameter methodParameter = MethodParameter.forExecutable(method, 0);
methodParameter.increaseNestingLevel(); methodParameter.increaseNestingLevel();
methodParameter.setTypeIndexForCurrentLevel(0); methodParameter.setTypeIndexForCurrentLevel(0);
ResolvableType type = ResolvableType.forMethodParameter(methodParameter); ResolvableType type = ResolvableType.forMethodParameter(methodParameter);
@ -782,7 +782,7 @@ public class ResolvableTypeTests {
@Test @Test
public void resolveTypeVariableFromMethodParameterType() throws Exception { public void resolveTypeVariableFromMethodParameterType() throws Exception {
Method method = Methods.class.getMethod("typedParameter", Object.class); Method method = Methods.class.getMethod("typedParameter", Object.class);
MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0); MethodParameter methodParameter = MethodParameter.forExecutable(method, 0);
ResolvableType type = ResolvableType.forMethodParameter(methodParameter); ResolvableType type = ResolvableType.forMethodParameter(methodParameter);
assertThat(type.resolve(), nullValue()); assertThat(type.resolve(), nullValue());
assertThat(type.getType().toString(), equalTo("T")); assertThat(type.getType().toString(), equalTo("T"));
@ -791,7 +791,7 @@ public class ResolvableTypeTests {
@Test @Test
public void resolveTypeVariableFromMethodParameterTypeWithImplementsClass() throws Exception { public void resolveTypeVariableFromMethodParameterTypeWithImplementsClass() throws Exception {
Method method = Methods.class.getMethod("typedParameter", Object.class); Method method = Methods.class.getMethod("typedParameter", Object.class);
MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0); MethodParameter methodParameter = MethodParameter.forExecutable(method, 0);
methodParameter.setContainingClass(TypedMethods.class); methodParameter.setContainingClass(TypedMethods.class);
ResolvableType type = ResolvableType.forMethodParameter(methodParameter); ResolvableType type = ResolvableType.forMethodParameter(methodParameter);
assertThat(type.resolve(), equalTo((Class) String.class)); assertThat(type.resolve(), equalTo((Class) String.class));
@ -801,7 +801,7 @@ public class ResolvableTypeTests {
@Test @Test
public void resolveTypeVariableFromMethodParameterTypeWithImplementsType() throws Exception { public void resolveTypeVariableFromMethodParameterTypeWithImplementsType() throws Exception {
Method method = Methods.class.getMethod("typedParameter", Object.class); Method method = Methods.class.getMethod("typedParameter", Object.class);
MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0); MethodParameter methodParameter = MethodParameter.forExecutable(method, 0);
ResolvableType implementationType = ResolvableType.forClassWithGenerics(Methods.class, Integer.class); ResolvableType implementationType = ResolvableType.forClassWithGenerics(Methods.class, Integer.class);
ResolvableType type = ResolvableType.forMethodParameter(methodParameter, implementationType); ResolvableType type = ResolvableType.forMethodParameter(methodParameter, implementationType);
assertThat(type.resolve(), equalTo((Class) Integer.class)); assertThat(type.resolve(), equalTo((Class) Integer.class));
@ -893,7 +893,7 @@ public class ResolvableTypeTests {
Field basicField = Fields.class.getField("classType"); Field basicField = Fields.class.getField("classType");
Field field = Fields.class.getField("charSequenceList"); Field field = Fields.class.getField("charSequenceList");
Method method = Methods.class.getMethod("charSequenceParameter", List.class); Method method = Methods.class.getMethod("charSequenceParameter", List.class);
MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0); MethodParameter methodParameter = MethodParameter.forExecutable(method, 0);
assertThat(ResolvableType.forField(basicField).getSource(), equalTo((Object) basicField)); assertThat(ResolvableType.forField(basicField).getSource(), equalTo((Object) basicField));
assertThat(ResolvableType.forField(field).getSource(), equalTo((Object) field)); assertThat(ResolvableType.forField(field).getSource(), equalTo((Object) field));
assertThat(ResolvableType.forMethodParameter(methodParameter).getSource(), equalTo((Object) methodParameter)); assertThat(ResolvableType.forMethodParameter(methodParameter).getSource(), equalTo((Object) methodParameter));

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -46,78 +46,78 @@ public class SerializableTypeWrapperTests {
public void forField() throws Exception { public void forField() throws Exception {
Type type = SerializableTypeWrapper.forField(Fields.class.getField("parameterizedType")); Type type = SerializableTypeWrapper.forField(Fields.class.getField("parameterizedType"));
assertThat(type.toString(), equalTo("java.util.List<java.lang.String>")); assertThat(type.toString(), equalTo("java.util.List<java.lang.String>"));
assertSerialzable(type); assertSerializable(type);
} }
@Test @Test
public void forMethodParameter() throws Exception { public void forMethodParameter() throws Exception {
Method method = Methods.class.getDeclaredMethod("method", Class.class, Object.class); Method method = Methods.class.getDeclaredMethod("method", Class.class, Object.class);
Type type = SerializableTypeWrapper.forMethodParameter(MethodParameter.forMethodOrConstructor(method, 0)); Type type = SerializableTypeWrapper.forMethodParameter(MethodParameter.forExecutable(method, 0));
assertThat(type.toString(), equalTo("java.lang.Class<T>")); assertThat(type.toString(), equalTo("java.lang.Class<T>"));
assertSerialzable(type); assertSerializable(type);
} }
@Test @Test
public void forConstructor() throws Exception { public void forConstructor() throws Exception {
Constructor<?> constructor = Constructors.class.getDeclaredConstructor(List.class); Constructor<?> constructor = Constructors.class.getDeclaredConstructor(List.class);
Type type = SerializableTypeWrapper.forMethodParameter(MethodParameter.forMethodOrConstructor(constructor, 0)); Type type = SerializableTypeWrapper.forMethodParameter(MethodParameter.forExecutable(constructor, 0));
assertThat(type.toString(), equalTo("java.util.List<java.lang.String>")); assertThat(type.toString(), equalTo("java.util.List<java.lang.String>"));
assertSerialzable(type); assertSerializable(type);
} }
@Test @Test
public void forGenericSuperClass() throws Exception { public void forGenericSuperClass() throws Exception {
Type type = SerializableTypeWrapper.forGenericSuperclass(ArrayList.class); Type type = SerializableTypeWrapper.forGenericSuperclass(ArrayList.class);
assertThat(type.toString(), equalTo("java.util.AbstractList<E>")); assertThat(type.toString(), equalTo("java.util.AbstractList<E>"));
assertSerialzable(type); assertSerializable(type);
} }
@Test @Test
public void forGenericInterfaces() throws Exception { public void forGenericInterfaces() throws Exception {
Type type = SerializableTypeWrapper.forGenericInterfaces(List.class)[0]; Type type = SerializableTypeWrapper.forGenericInterfaces(List.class)[0];
assertThat(type.toString(), equalTo("java.util.Collection<E>")); assertThat(type.toString(), equalTo("java.util.Collection<E>"));
assertSerialzable(type); assertSerializable(type);
} }
@Test @Test
public void forTypeParamters() throws Exception { public void forTypeParamters() throws Exception {
Type type = SerializableTypeWrapper.forTypeParameters(List.class)[0]; Type type = SerializableTypeWrapper.forTypeParameters(List.class)[0];
assertThat(type.toString(), equalTo("E")); assertThat(type.toString(), equalTo("E"));
assertSerialzable(type); assertSerializable(type);
} }
@Test @Test
public void classType() throws Exception { public void classType() throws Exception {
Type type = SerializableTypeWrapper.forField(Fields.class.getField("classType")); Type type = SerializableTypeWrapper.forField(Fields.class.getField("classType"));
assertThat(type.toString(), equalTo("class java.lang.String")); assertThat(type.toString(), equalTo("class java.lang.String"));
assertSerialzable(type); assertSerializable(type);
} }
@Test @Test
public void genericArrayType() throws Exception { public void genericArrayType() throws Exception {
GenericArrayType type = (GenericArrayType) SerializableTypeWrapper.forField(Fields.class.getField("genericArrayType")); GenericArrayType type = (GenericArrayType) SerializableTypeWrapper.forField(Fields.class.getField("genericArrayType"));
assertThat(type.toString(), equalTo("java.util.List<java.lang.String>[]")); assertThat(type.toString(), equalTo("java.util.List<java.lang.String>[]"));
assertSerialzable(type); assertSerializable(type);
assertSerialzable(type.getGenericComponentType()); assertSerializable(type.getGenericComponentType());
} }
@Test @Test
public void parameterizedType() throws Exception { public void parameterizedType() throws Exception {
ParameterizedType type = (ParameterizedType) SerializableTypeWrapper.forField(Fields.class.getField("parameterizedType")); ParameterizedType type = (ParameterizedType) SerializableTypeWrapper.forField(Fields.class.getField("parameterizedType"));
assertThat(type.toString(), equalTo("java.util.List<java.lang.String>")); assertThat(type.toString(), equalTo("java.util.List<java.lang.String>"));
assertSerialzable(type); assertSerializable(type);
assertSerialzable(type.getOwnerType()); assertSerializable(type.getOwnerType());
assertSerialzable(type.getRawType()); assertSerializable(type.getRawType());
assertSerialzable(type.getActualTypeArguments()); assertSerializable(type.getActualTypeArguments());
assertSerialzable(type.getActualTypeArguments()[0]); assertSerializable(type.getActualTypeArguments()[0]);
} }
@Test @Test
public void typeVariableType() throws Exception { public void typeVariableType() throws Exception {
TypeVariable<?> type = (TypeVariable<?>) SerializableTypeWrapper.forField(Fields.class.getField("typeVariableType")); TypeVariable<?> type = (TypeVariable<?>) SerializableTypeWrapper.forField(Fields.class.getField("typeVariableType"));
assertThat(type.toString(), equalTo("T")); assertThat(type.toString(), equalTo("T"));
assertSerialzable(type); assertSerializable(type);
assertSerialzable(type.getBounds()); assertSerializable(type.getBounds());
} }
@Test @Test
@ -125,13 +125,13 @@ public class SerializableTypeWrapperTests {
ParameterizedType typeSource = (ParameterizedType) SerializableTypeWrapper.forField(Fields.class.getField("wildcardType")); ParameterizedType typeSource = (ParameterizedType) SerializableTypeWrapper.forField(Fields.class.getField("wildcardType"));
WildcardType type = (WildcardType) typeSource.getActualTypeArguments()[0]; WildcardType type = (WildcardType) typeSource.getActualTypeArguments()[0];
assertThat(type.toString(), equalTo("? extends java.lang.CharSequence")); assertThat(type.toString(), equalTo("? extends java.lang.CharSequence"));
assertSerialzable(type); assertSerializable(type);
assertSerialzable(type.getLowerBounds()); assertSerializable(type.getLowerBounds());
assertSerialzable(type.getUpperBounds()); assertSerializable(type.getUpperBounds());
} }
private void assertSerialzable(Object source) throws Exception { private void assertSerializable(Object source) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos); ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(source); oos.writeObject(source);
@ -152,19 +152,19 @@ public class SerializableTypeWrapperTests {
public T typeVariableType; public T typeVariableType;
public List<? extends CharSequence> wildcardType; public List<? extends CharSequence> wildcardType;
} }
static interface Methods {
interface Methods {
<T> List<T> method(Class<T> p1, T p2); <T> List<T> method(Class<T> p1, T p2);
} }
static class Constructors { static class Constructors {
public Constructors(List<String> p) { public Constructors(List<String> p) {
} }
} }
} }

View File

@ -0,0 +1,109 @@
/*
* 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.core.annotation;
import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import static org.junit.Assert.*;
/**
* @author Juergen Hoeller
*/
public class SynthesizingMethodParameterTests {
private Method method;
private SynthesizingMethodParameter stringParameter;
private SynthesizingMethodParameter longParameter;
private SynthesizingMethodParameter intReturnType;
@Before
public void setUp() throws NoSuchMethodException {
method = getClass().getMethod("method", String.class, Long.TYPE);
stringParameter = new SynthesizingMethodParameter(method, 0);
longParameter = new SynthesizingMethodParameter(method, 1);
intReturnType = new SynthesizingMethodParameter(method, -1);
}
@Test
public void testEquals() throws NoSuchMethodException {
assertEquals(stringParameter, stringParameter);
assertEquals(longParameter, longParameter);
assertEquals(intReturnType, intReturnType);
assertFalse(stringParameter.equals(longParameter));
assertFalse(stringParameter.equals(intReturnType));
assertFalse(longParameter.equals(stringParameter));
assertFalse(longParameter.equals(intReturnType));
assertFalse(intReturnType.equals(stringParameter));
assertFalse(intReturnType.equals(longParameter));
Method method = getClass().getMethod("method", String.class, Long.TYPE);
MethodParameter methodParameter = new SynthesizingMethodParameter(method, 0);
assertEquals(stringParameter, methodParameter);
assertEquals(methodParameter, stringParameter);
assertNotEquals(longParameter, methodParameter);
assertNotEquals(methodParameter, longParameter);
methodParameter = new MethodParameter(method, 0);
assertNotEquals(stringParameter, methodParameter);
assertNotEquals(methodParameter, stringParameter);
assertNotEquals(longParameter, methodParameter);
assertNotEquals(methodParameter, longParameter);
}
@Test
public void testHashCode() throws NoSuchMethodException {
assertEquals(stringParameter.hashCode(), stringParameter.hashCode());
assertEquals(longParameter.hashCode(), longParameter.hashCode());
assertEquals(intReturnType.hashCode(), intReturnType.hashCode());
Method method = getClass().getMethod("method", String.class, Long.TYPE);
SynthesizingMethodParameter methodParameter = new SynthesizingMethodParameter(method, 0);
assertEquals(stringParameter.hashCode(), methodParameter.hashCode());
assertNotEquals(longParameter.hashCode(), methodParameter.hashCode());
}
@Test
public void testFactoryMethods() {
assertEquals(stringParameter, SynthesizingMethodParameter.forExecutable(method, 0));
assertEquals(longParameter, SynthesizingMethodParameter.forExecutable(method, 1));
assertEquals(stringParameter, SynthesizingMethodParameter.forParameter(method.getParameters()[0]));
assertEquals(longParameter, SynthesizingMethodParameter.forParameter(method.getParameters()[1]));
}
@Test(expected = IllegalArgumentException.class)
public void testIndexValidation() {
new SynthesizingMethodParameter(method, 2);
}
public int method(String p1, long p2) {
return 42;
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2015 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@
package org.springframework.expression.spel.support; package org.springframework.expression.spel.support;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.lang.reflect.Executable;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.List; import java.util.List;
@ -241,7 +242,7 @@ public class ReflectionHelper {
public static boolean convertAllArguments(TypeConverter converter, Object[] arguments, Method method) public static boolean convertAllArguments(TypeConverter converter, Object[] arguments, Method method)
throws SpelEvaluationException { throws SpelEvaluationException {
Integer varargsPosition = (method.isVarArgs() ? method.getParameterTypes().length - 1 : null); Integer varargsPosition = (method.isVarArgs() ? method.getParameterCount() - 1 : null);
return convertArguments(converter, arguments, method, varargsPosition); return convertArguments(converter, arguments, method, varargsPosition);
} }
@ -250,19 +251,19 @@ public class ReflectionHelper {
* required parameter types. The arguments are converted 'in-place' in the input array. * required parameter types. The arguments are converted 'in-place' in the input array.
* @param converter the type converter to use for attempting conversions * @param converter the type converter to use for attempting conversions
* @param arguments the actual arguments that need conversion * @param arguments the actual arguments that need conversion
* @param methodOrCtor the target Method or Constructor * @param executable the target Method or Constructor
* @param varargsPosition the known position of the varargs argument, if any * @param varargsPosition the known position of the varargs argument, if any
* ({@code null} if not varargs) * ({@code null} if not varargs)
* @return {@code true} if some kind of conversion occurred on an argument * @return {@code true} if some kind of conversion occurred on an argument
* @throws EvaluationException if a problem occurs during conversion * @throws EvaluationException if a problem occurs during conversion
*/ */
static boolean convertArguments(TypeConverter converter, Object[] arguments, Object methodOrCtor, static boolean convertArguments(TypeConverter converter, Object[] arguments, Executable executable,
Integer varargsPosition) throws EvaluationException { Integer varargsPosition) throws EvaluationException {
boolean conversionOccurred = false; boolean conversionOccurred = false;
if (varargsPosition == null) { if (varargsPosition == null) {
for (int i = 0; i < arguments.length; i++) { for (int i = 0; i < arguments.length; i++) {
TypeDescriptor targetType = new TypeDescriptor(MethodParameter.forMethodOrConstructor(methodOrCtor, i)); TypeDescriptor targetType = new TypeDescriptor(MethodParameter.forExecutable(executable, i));
Object argument = arguments[i]; Object argument = arguments[i];
arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType); arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType);
conversionOccurred |= (argument != arguments[i]); conversionOccurred |= (argument != arguments[i]);
@ -271,12 +272,12 @@ public class ReflectionHelper {
else { else {
// Convert everything up to the varargs position // Convert everything up to the varargs position
for (int i = 0; i < varargsPosition; i++) { for (int i = 0; i < varargsPosition; i++) {
TypeDescriptor targetType = new TypeDescriptor(MethodParameter.forMethodOrConstructor(methodOrCtor, i)); TypeDescriptor targetType = new TypeDescriptor(MethodParameter.forExecutable(executable, i));
Object argument = arguments[i]; Object argument = arguments[i];
arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType); arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType);
conversionOccurred |= (argument != arguments[i]); conversionOccurred |= (argument != arguments[i]);
} }
MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, varargsPosition); MethodParameter methodParam = MethodParameter.forExecutable(executable, varargsPosition);
if (varargsPosition == arguments.length - 1) { if (varargsPosition == arguments.length - 1) {
// If the target is varargs and there is just one more argument // If the target is varargs and there is just one more argument
// then convert it here // then convert it here