diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java index 9d1455c814..d2e486c86d 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java @@ -18,7 +18,7 @@ package org.springframework.beans.factory.support; import java.beans.ConstructorProperties; import java.lang.reflect.Constructor; -import java.lang.reflect.Member; +import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.security.AccessController; @@ -506,7 +506,7 @@ class ConstructorResolver { // and explicitly ignore overridden methods (with the same parameter signature). else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight && !mbd.isLenientConstructorResolution() && - paramTypes.length == factoryMethodToUse.getParameterTypes().length && + paramTypes.length == factoryMethodToUse.getParameterCount() && !Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes())) { if (ambiguousFactoryMethods == null) { ambiguousFactoryMethods = new LinkedHashSet<>(); @@ -662,7 +662,7 @@ class ConstructorResolver { */ private ArgumentsHolder createArgumentArray( 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 { TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ? @@ -699,7 +699,7 @@ class ConstructorResolver { ConstructorArgumentValues.ValueHolder sourceHolder = (ConstructorArgumentValues.ValueHolder) valueHolder.getSource(); Object sourceValue = sourceHolder.getValue(); - MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex); + MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex); try { convertedValue = converter.convertIfNecessary(originalValue, paramType, methodParam); // TODO re-enable once race condition has been found (SPR-7423) @@ -727,7 +727,7 @@ class ConstructorResolver { args.rawArguments[paramIndex] = originalValue; } else { - MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex); + MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex); // No explicit match found: we're either supposed to autowire or // have to fail creating an argument array for the given constructor. if (!autowiring) { @@ -755,7 +755,7 @@ class ConstructorResolver { this.beanFactory.registerDependentBean(autowiredBeanName, beanName); if (this.beanFactory.logger.isDebugEnabled()) { 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 + "'"); } } @@ -767,10 +767,9 @@ class ConstructorResolver { * Resolve the prepared arguments stored in the given bean definition. */ 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 ? - ((Method) methodOrCtor).getParameterTypes() : ((Constructor) methodOrCtor).getParameterTypes()); + Class[] paramTypes = executable.getParameterTypes(); TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ? this.beanFactory.getCustomTypeConverter() : bw); BeanDefinitionValueResolver valueResolver = @@ -778,8 +777,8 @@ class ConstructorResolver { Object[] resolvedArgs = new Object[argsToResolve.length]; for (int argIndex = 0; argIndex < argsToResolve.length; argIndex++) { Object argValue = argsToResolve[argIndex]; - MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, argIndex); - GenericTypeResolver.resolveParameterType(methodParam, methodOrCtor.getDeclaringClass()); + MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex); + GenericTypeResolver.resolveParameterType(methodParam, executable.getDeclaringClass()); if (argValue instanceof AutowiredArgumentMarker) { argValue = resolveAutowiredArgument(methodParam, beanName, null, converter); } diff --git a/spring-core/src/main/java/org/springframework/core/MethodParameter.java b/spring-core/src/main/java/org/springframework/core/MethodParameter.java index 223a48ff4a..150f8cfeb8 100644 --- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java @@ -19,8 +19,10 @@ package org.springframework.core; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; import java.lang.reflect.Member; import java.lang.reflect.Method; +import java.lang.reflect.Parameter; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.HashMap; @@ -54,6 +56,8 @@ public class MethodParameter { private final int parameterIndex; + private volatile Parameter parameter; + private int nestingLevel = 1; /** Map from Integer level to Integer type index */ @@ -98,7 +102,7 @@ public class MethodParameter { public MethodParameter(Method method, int parameterIndex, int nestingLevel) { Assert.notNull(method, "Method must not be null"); this.method = method; - this.parameterIndex = parameterIndex; + this.parameterIndex = validateIndex(method, parameterIndex); this.nestingLevel = nestingLevel; this.constructor = null; } @@ -123,7 +127,7 @@ public class MethodParameter { public MethodParameter(Constructor constructor, int parameterIndex, int nestingLevel) { Assert.notNull(constructor, "Constructor must not be null"); this.constructor = constructor; - this.parameterIndex = parameterIndex; + this.parameterIndex = validateIndex(constructor, parameterIndex); this.nestingLevel = nestingLevel; this.method = null; } @@ -138,6 +142,7 @@ public class MethodParameter { this.method = original.method; this.constructor = original.constructor; this.parameterIndex = original.parameterIndex; + this.parameter = original.parameter; this.nestingLevel = original.nestingLevel; this.typeIndexesPerLevel = original.typeIndexesPerLevel; this.containingClass = original.containingClass; @@ -179,15 +184,7 @@ public class MethodParameter { * @return the Method or Constructor as Member */ public Member getMember() { - // NOTE: no ternary expression to retain JDK <8 compatibility even when using - // 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; - } + return getExecutable(); } /** @@ -197,15 +194,27 @@ public class MethodParameter { * @return the Method or Constructor as AnnotatedElement */ public AnnotatedElement getAnnotatedElement() { - // NOTE: no ternary expression to retain JDK <8 compatibility even when using - // 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; + return getExecutable(); + } + + /** + * Return the wrapped executable. + * @return the Method or Constructor as Executable + * @since 5.0 + */ + 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) { return true; } - if (!(other instanceof MethodParameter)) { + if (other == null || getClass() != other.getClass()) { return false; } MethodParameter otherParam = (MethodParameter) other; - return (this.parameterIndex == otherParam.parameterIndex && getMember().equals(otherParam.getMember())); + return (this.parameterIndex == otherParam.parameterIndex && getExecutable().equals(otherParam.getExecutable())); } @Override @@ -596,23 +605,73 @@ public class MethodParameter { /** * Create a new MethodParameter for the given method or constructor. - *

This is a convenience constructor for scenarios where a + *

This is a convenience factory method for scenarios where a * Method or Constructor reference is treated in a generic fashion. * @param methodOrConstructor the Method or Constructor to specify a parameter for * @param parameterIndex the index of the parameter * @return the corresponding MethodParameter instance + * @deprecated as of 5.0, in favor of {@link #forExecutable} */ + @Deprecated public static MethodParameter forMethodOrConstructor(Object methodOrConstructor, int parameterIndex) { - if (methodOrConstructor instanceof Method) { - return new MethodParameter((Method) methodOrConstructor, parameterIndex); - } - else if (methodOrConstructor instanceof Constructor) { - return new MethodParameter((Constructor) methodOrConstructor, parameterIndex); - } - else { + if (!(methodOrConstructor instanceof Executable)) { throw new IllegalArgumentException( "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. + *

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. + *

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; } } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java index eb287c478d..9f76491246 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java @@ -18,7 +18,9 @@ package org.springframework.core.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; import java.lang.reflect.Method; +import java.lang.reflect.Parameter; import org.springframework.core.MethodParameter; @@ -108,4 +110,38 @@ public class SynthesizingMethodParameter extends MethodParameter { return new SynthesizingMethodParameter(this); } + + /** + * Create a new SynthesizingMethodParameter for the given method or constructor. + *

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. + *

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)); + } + } diff --git a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java index 78e5eb8487..364bffb623 100644 --- a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java @@ -158,7 +158,7 @@ public class GenericTypeResolverTests { @Test public void getGenericsOnArrayFromParamCannotBeResolved() throws Exception { // SPR-11044 - MethodParameter methodParameter = MethodParameter.forMethodOrConstructor( + MethodParameter methodParameter = MethodParameter.forExecutable( WithArrayBase.class.getDeclaredMethod("array", Object[].class), 0); Class resolved = GenericTypeResolver.resolveParameterType(methodParameter, WithArray.class); assertThat(resolved, equalTo((Class) Object[].class)); diff --git a/spring-core/src/test/java/org/springframework/core/MethodParameterTests.java b/spring-core/src/test/java/org/springframework/core/MethodParameterTests.java index c3c3e7d139..6d5dc18f27 100644 --- a/spring-core/src/test/java/org/springframework/core/MethodParameterTests.java +++ b/spring-core/src/test/java/org/springframework/core/MethodParameterTests.java @@ -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"); * 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 Juergen Hoeller */ public class MethodParameterTests { + private Method method; + private MethodParameter stringParameter; private MethodParameter longParameter; @@ -37,12 +40,13 @@ public class MethodParameterTests { @Before 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); longParameter = new MethodParameter(method, 1); intReturnType = new MethodParameter(method, -1); } + @Test public void testEquals() throws NoSuchMethodException { assertEquals(stringParameter, stringParameter); @@ -60,8 +64,8 @@ public class MethodParameterTests { MethodParameter methodParameter = new MethodParameter(method, 0); assertEquals(stringParameter, methodParameter); assertEquals(methodParameter, stringParameter); - assertFalse(longParameter.equals(methodParameter)); - assertFalse(methodParameter.equals(longParameter)); + assertNotEquals(longParameter, methodParameter); + assertNotEquals(methodParameter, longParameter); } @Test @@ -73,7 +77,25 @@ public class MethodParameterTests { Method method = getClass().getMethod("method", String.class, Long.TYPE); MethodParameter methodParameter = new MethodParameter(method, 0); 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); } diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java index f84d523e36..3dfaeee065 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -214,7 +214,7 @@ public class ResolvableTypeTests { @Test public void forMethodParameter() throws Exception { 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); assertThat(type.getType(), equalTo(method.getGenericParameterTypes()[0])); } @@ -222,7 +222,7 @@ public class ResolvableTypeTests { @Test public void forMethodParameterWithNesting() throws Exception { Method method = Methods.class.getMethod("nested", Map.class); - MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0); + MethodParameter methodParameter = MethodParameter.forExecutable(method, 0); methodParameter.increaseNestingLevel(); ResolvableType type = ResolvableType.forMethodParameter(methodParameter); assertThat(type.resolve(), equalTo((Class) Map.class)); @@ -233,7 +233,7 @@ public class ResolvableTypeTests { @Test public void forMethodParameterWithNestingAndLevels() throws Exception { Method method = Methods.class.getMethod("nested", Map.class); - MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0); + MethodParameter methodParameter = MethodParameter.forExecutable(method, 0); methodParameter.increaseNestingLevel(); methodParameter.setTypeIndexForCurrentLevel(0); ResolvableType type = ResolvableType.forMethodParameter(methodParameter); @@ -782,7 +782,7 @@ public class ResolvableTypeTests { @Test public void resolveTypeVariableFromMethodParameterType() throws Exception { 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); assertThat(type.resolve(), nullValue()); assertThat(type.getType().toString(), equalTo("T")); @@ -791,7 +791,7 @@ public class ResolvableTypeTests { @Test public void resolveTypeVariableFromMethodParameterTypeWithImplementsClass() throws Exception { Method method = Methods.class.getMethod("typedParameter", Object.class); - MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0); + MethodParameter methodParameter = MethodParameter.forExecutable(method, 0); methodParameter.setContainingClass(TypedMethods.class); ResolvableType type = ResolvableType.forMethodParameter(methodParameter); assertThat(type.resolve(), equalTo((Class) String.class)); @@ -801,7 +801,7 @@ public class ResolvableTypeTests { @Test public void resolveTypeVariableFromMethodParameterTypeWithImplementsType() throws Exception { 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 type = ResolvableType.forMethodParameter(methodParameter, implementationType); assertThat(type.resolve(), equalTo((Class) Integer.class)); @@ -893,7 +893,7 @@ public class ResolvableTypeTests { Field basicField = Fields.class.getField("classType"); Field field = Fields.class.getField("charSequenceList"); 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(field).getSource(), equalTo((Object) field)); assertThat(ResolvableType.forMethodParameter(methodParameter).getSource(), equalTo((Object) methodParameter)); diff --git a/spring-core/src/test/java/org/springframework/core/SerializableTypeWrapperTests.java b/spring-core/src/test/java/org/springframework/core/SerializableTypeWrapperTests.java index ac68dacec6..a9853c3894 100644 --- a/spring-core/src/test/java/org/springframework/core/SerializableTypeWrapperTests.java +++ b/spring-core/src/test/java/org/springframework/core/SerializableTypeWrapperTests.java @@ -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"); * you may not use this file except in compliance with the License. @@ -46,78 +46,78 @@ public class SerializableTypeWrapperTests { public void forField() throws Exception { Type type = SerializableTypeWrapper.forField(Fields.class.getField("parameterizedType")); assertThat(type.toString(), equalTo("java.util.List")); - assertSerialzable(type); + assertSerializable(type); } @Test public void forMethodParameter() throws Exception { 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")); - assertSerialzable(type); + assertSerializable(type); } @Test public void forConstructor() throws Exception { 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")); - assertSerialzable(type); + assertSerializable(type); } @Test public void forGenericSuperClass() throws Exception { Type type = SerializableTypeWrapper.forGenericSuperclass(ArrayList.class); assertThat(type.toString(), equalTo("java.util.AbstractList")); - assertSerialzable(type); + assertSerializable(type); } @Test public void forGenericInterfaces() throws Exception { Type type = SerializableTypeWrapper.forGenericInterfaces(List.class)[0]; assertThat(type.toString(), equalTo("java.util.Collection")); - assertSerialzable(type); + assertSerializable(type); } @Test public void forTypeParamters() throws Exception { Type type = SerializableTypeWrapper.forTypeParameters(List.class)[0]; assertThat(type.toString(), equalTo("E")); - assertSerialzable(type); + assertSerializable(type); } @Test public void classType() throws Exception { Type type = SerializableTypeWrapper.forField(Fields.class.getField("classType")); assertThat(type.toString(), equalTo("class java.lang.String")); - assertSerialzable(type); + assertSerializable(type); } @Test public void genericArrayType() throws Exception { GenericArrayType type = (GenericArrayType) SerializableTypeWrapper.forField(Fields.class.getField("genericArrayType")); assertThat(type.toString(), equalTo("java.util.List[]")); - assertSerialzable(type); - assertSerialzable(type.getGenericComponentType()); + assertSerializable(type); + assertSerializable(type.getGenericComponentType()); } @Test public void parameterizedType() throws Exception { ParameterizedType type = (ParameterizedType) SerializableTypeWrapper.forField(Fields.class.getField("parameterizedType")); assertThat(type.toString(), equalTo("java.util.List")); - assertSerialzable(type); - assertSerialzable(type.getOwnerType()); - assertSerialzable(type.getRawType()); - assertSerialzable(type.getActualTypeArguments()); - assertSerialzable(type.getActualTypeArguments()[0]); + assertSerializable(type); + assertSerializable(type.getOwnerType()); + assertSerializable(type.getRawType()); + assertSerializable(type.getActualTypeArguments()); + assertSerializable(type.getActualTypeArguments()[0]); } @Test public void typeVariableType() throws Exception { TypeVariable type = (TypeVariable) SerializableTypeWrapper.forField(Fields.class.getField("typeVariableType")); assertThat(type.toString(), equalTo("T")); - assertSerialzable(type); - assertSerialzable(type.getBounds()); + assertSerializable(type); + assertSerializable(type.getBounds()); } @Test @@ -125,13 +125,13 @@ public class SerializableTypeWrapperTests { ParameterizedType typeSource = (ParameterizedType) SerializableTypeWrapper.forField(Fields.class.getField("wildcardType")); WildcardType type = (WildcardType) typeSource.getActualTypeArguments()[0]; assertThat(type.toString(), equalTo("? extends java.lang.CharSequence")); - assertSerialzable(type); - assertSerialzable(type.getLowerBounds()); - assertSerialzable(type.getUpperBounds()); + assertSerializable(type); + assertSerializable(type.getLowerBounds()); + assertSerializable(type.getUpperBounds()); } - private void assertSerialzable(Object source) throws Exception { + private void assertSerializable(Object source) throws Exception { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(source); @@ -152,19 +152,19 @@ public class SerializableTypeWrapperTests { public T typeVariableType; public List wildcardType; - } - static interface Methods { + + interface Methods { List method(Class p1, T p2); - } + static class Constructors { public Constructors(List p) { } - } + } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/SynthesizingMethodParameterTests.java b/spring-core/src/test/java/org/springframework/core/annotation/SynthesizingMethodParameterTests.java new file mode 100644 index 0000000000..fad2fab7af --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/annotation/SynthesizingMethodParameterTests.java @@ -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; + } + +} diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java index d080c284f0..4c721da50e 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java @@ -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"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.expression.spel.support; import java.lang.reflect.Array; +import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.util.List; @@ -241,7 +242,7 @@ public class ReflectionHelper { public static boolean convertAllArguments(TypeConverter converter, Object[] arguments, Method method) 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); } @@ -250,19 +251,19 @@ public class ReflectionHelper { * required parameter types. The arguments are converted 'in-place' in the input array. * @param converter the type converter to use for attempting conversions * @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 * ({@code null} if not varargs) * @return {@code true} if some kind of conversion occurred on an argument * @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 { boolean conversionOccurred = false; if (varargsPosition == null) { 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]; arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType); conversionOccurred |= (argument != arguments[i]); @@ -271,12 +272,12 @@ public class ReflectionHelper { else { // Convert everything up to the varargs position 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]; arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType); conversionOccurred |= (argument != arguments[i]); } - MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, varargsPosition); + MethodParameter methodParam = MethodParameter.forExecutable(executable, varargsPosition); if (varargsPosition == arguments.length - 1) { // If the target is varargs and there is just one more argument // then convert it here