From 5f6b04251e1643d7ba22fcff708fcf196a36dcc6 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 29 Mar 2018 15:45:02 +0200 Subject: [PATCH] Workaround for inner class constructor parameter annotation bug in javac Issue: SPR-16652 (cherry picked from commit 53d0139) --- .../springframework/core/MethodParameter.java | 25 +++++-- .../core/MethodParameterTests.java | 68 +++++++++++++++++-- 2 files changed, 81 insertions(+), 12 deletions(-) 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 5bc50d97312..7267704ed45 100644 --- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -21,6 +21,7 @@ import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.lang.reflect.Member; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.HashMap; @@ -47,6 +48,8 @@ import org.springframework.util.ClassUtils; */ public class MethodParameter { + private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; + private static final Class javaUtilOptionalClass; static { @@ -482,17 +485,27 @@ public class MethodParameter { * Return the annotations associated with the specific method/constructor parameter. */ public Annotation[] getParameterAnnotations() { - if (this.parameterAnnotations == null) { + Annotation[] paramAnns = this.parameterAnnotations; + if (paramAnns == null) { Annotation[][] annotationArray = (this.method != null ? this.method.getParameterAnnotations() : this.constructor.getParameterAnnotations()); - if (this.parameterIndex >= 0 && this.parameterIndex < annotationArray.length) { - this.parameterAnnotations = adaptAnnotationArray(annotationArray[this.parameterIndex]); + int index = this.parameterIndex; + if (this.constructor != null && this.constructor.getDeclaringClass().isMemberClass() && + !Modifier.isStatic(this.constructor.getDeclaringClass().getModifiers()) && + annotationArray.length == this.constructor.getParameterTypes().length - 1) { + // Bug in javac in JDK <9: annotation array excludes enclosing instance parameter + // for inner classes, so access it with the actual parameter index lowered by 1 + index = this.parameterIndex - 1; + } + if (index >= 0 && index < annotationArray.length) { + paramAnns = adaptAnnotationArray(annotationArray[index]); } else { - this.parameterAnnotations = new Annotation[0]; + paramAnns = EMPTY_ANNOTATION_ARRAY; } + this.parameterAnnotations = paramAnns; } - return this.parameterAnnotations; + return paramAnns; } /** 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 c3c3e7d1399..c889f745c44 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-2018 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. @@ -16,6 +16,11 @@ package org.springframework.core; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Constructor; import java.lang.reflect.Method; import org.junit.Before; @@ -25,9 +30,13 @@ import static org.junit.Assert.*; /** * @author Arjen Poutsma + * @author Juergen Hoeller + * @author Sam Brannen */ public class MethodParameterTests { + private Method method; + private MethodParameter stringParameter; private MethodParameter longParameter; @@ -36,13 +45,14 @@ public class MethodParameterTests { @Before - public void setUp() throws NoSuchMethodException { - Method method = getClass().getMethod("method", String.class, Long.TYPE); + public void setup() throws NoSuchMethodException { + 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 +70,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 +83,34 @@ 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 + public void annotatedConstructorParameterInStaticNestedClass() throws Exception { + Constructor constructor = NestedClass.class.getDeclaredConstructor(String.class); + MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(constructor, 0); + assertEquals(String.class, methodParameter.getParameterType()); + assertNotNull("Failed to find @Param annotation", methodParameter.getParameterAnnotation(Param.class)); + assertNotNull(methodParameter.getParameterAnnotation(Param.class)); + } + + @Test // SPR-16652 + public void annotatedConstructorParameterInInnerClass() throws Exception { + Constructor constructor = InnerClass.class.getConstructor(getClass(), String.class, Integer.class); + + MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(constructor, 0); + assertEquals(getClass(), methodParameter.getParameterType()); + assertNull(methodParameter.getParameterAnnotation(Param.class)); + + methodParameter = MethodParameter.forMethodOrConstructor(constructor, 1); + assertEquals(String.class, methodParameter.getParameterType()); + // The following assertion currently fails if this test class is compiled using JDK 8. + assertNotNull("Failed to find @Param annotation", methodParameter.getParameterAnnotation(Param.class)); + + methodParameter = MethodParameter.forMethodOrConstructor(constructor, 2); + assertEquals(Integer.class, methodParameter.getParameterType()); + assertNull(methodParameter.getParameterAnnotation(Param.class)); } @@ -81,4 +118,23 @@ public class MethodParameterTests { return 42; } + @SuppressWarnings("unused") + private static class NestedClass { + + NestedClass(@Param String s) { + } + } + + @SuppressWarnings("unused") + private class InnerClass { + + public InnerClass(@Param String s, Integer i) { + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.PARAMETER) + private @interface Param { + } + }