--- old/src/java.base/share/classes/java/lang/reflect/Constructor.java 2017-05-18 00:09:23.333355526 -0700 +++ new/src/java.base/share/classes/java/lang/reflect/Constructor.java 2017-05-18 00:09:23.229355531 -0700 @@ -588,19 +588,18 @@ } @Override - void handleParameterNumberMismatch(int resultLength, int numParameters) { + boolean handleParameterNumberMismatch(int resultLength, int numParameters) { Class declaringClass = getDeclaringClass(); if (declaringClass.isEnum() || declaringClass.isAnonymousClass() || declaringClass.isLocalClass() ) - return ; // Can't do reliable parameter counting + return false; // Can't do reliable parameter counting else { - if (!declaringClass.isMemberClass() || // top-level - // Check for the enclosing instance parameter for - // non-static member classes - (declaringClass.isMemberClass() && - ((declaringClass.getModifiers() & Modifier.STATIC) == 0) && - resultLength + 1 != numParameters) ) { + if (declaringClass.isMemberClass() && + ((declaringClass.getModifiers() & Modifier.STATIC) == 0) && + resultLength + 1 == numParameters) { + return true; + } else { throw new AnnotationFormatError( "Parameter annotations don't match number of parameters"); } --- old/src/java.base/share/classes/java/lang/reflect/Executable.java 2017-05-18 00:09:23.693355511 -0700 +++ new/src/java.base/share/classes/java/lang/reflect/Executable.java 2017-05-18 00:09:23.585355515 -0700 @@ -551,12 +551,18 @@ Annotation[][] result = parseParameterAnnotations(parameterAnnotations); - if (result.length != numParameters) - handleParameterNumberMismatch(result.length, numParameters); + if (result.length != numParameters && + handleParameterNumberMismatch(result.length, numParameters)) { + Annotation[][] tmp = new Annotation[result.length+1][]; + // Shift annotations down one to account for an implicit leading parameter + System.arraycopy(result, 0, tmp, 1, result.length); + tmp[0] = new Annotation[0]; + result = tmp; + } return result; } - abstract void handleParameterNumberMismatch(int resultLength, int numParameters); + abstract boolean handleParameterNumberMismatch(int resultLength, int numParameters); /** * {@inheritDoc} --- old/src/java.base/share/classes/java/lang/reflect/Method.java 2017-05-18 00:09:24.057355495 -0700 +++ new/src/java.base/share/classes/java/lang/reflect/Method.java 2017-05-18 00:09:23.949355500 -0700 @@ -719,7 +719,7 @@ } @Override - void handleParameterNumberMismatch(int resultLength, int numParameters) { + boolean handleParameterNumberMismatch(int resultLength, int numParameters) { throw new AnnotationFormatError("Parameter annotations don't match number of parameters"); } } --- old/src/java.base/share/classes/sun/reflect/annotation/TypeAnnotationParser.java 2017-05-18 00:09:24.445355478 -0700 +++ new/src/java.base/share/classes/sun/reflect/annotation/TypeAnnotationParser.java 2017-05-18 00:09:24.337355483 -0700 @@ -123,9 +123,30 @@ tmp.add(t); } } + // If a constructor has a mandated outer this, it has no + // annotations and the annotations to parameter mapping should + // be offset by 1. + boolean offset = false; + if (decl instanceof Constructor) { + Constructor ctor = (Constructor) decl; + Class declaringClass = ctor.getDeclaringClass(); + if (!declaringClass.isEnum() && + (declaringClass.isMemberClass() && + (declaringClass.getModifiers() & Modifier.STATIC) == 0) ) { + } + offset = true; + } for (int i = 0; i < size; i++) { - @SuppressWarnings("unchecked") - ArrayList list = l[i]; + ArrayList list; + if (offset) { + @SuppressWarnings("unchecked") + ArrayList tmp = (i == 0) ? null : l[i - 1]; + list = tmp; + } else { + @SuppressWarnings("unchecked") + ArrayList tmp = l[i]; + list = tmp; + } TypeAnnotation[] typeAnnotations; if (list != null) { typeAnnotations = list.toArray(new TypeAnnotation[list.size()]); --- /dev/null 2017-02-08 09:46:36.598049496 -0800 +++ new/test/java/lang/annotation/TestConstructorParameterAnnotations.java 2017-05-18 00:09:24.685355468 -0700 @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8074977 + * @summary Test consistency of annotations on constructor parameters + * @compile TestConstructorParameterAnnotations.java + * @run main TestConstructorParameterAnnotations + * @compile -parameters TestConstructorParameterAnnotations.java + * @run main TestConstructorParameterAnnotations + */ + +import java.lang.annotation.*; +import java.lang.reflect.*; +import java.util.*; + +/* + * Some constructor parameters are mandated; that is, they + * are not explicitly present in the source code, but required to be + * present by the Java Language Specification. In other cases, some + * constructor parameters are not present in the source, but are + * synthesized by the compiler as an implementation artifact. There is + * not a reliable mechanism to consistently determine whether or not + * a parameter is implicit or not. + * + * (Using the "-parameters" option to javac does emit the information + * needed to make a reliably determination, but the information is not + * present by default.) + * + * The lack of such a mechanism causes complications reading parameter + * annotations in some cases since annotations for parameters are + * written out for the parameters in the source code, but when reading + * annotations at runtime all the parameters, including implicit ones, + * are present. + */ +public class TestConstructorParameterAnnotations { + public static void main(String... args) { + int errors = 0; + Class[] classes = {NestedClass0.class, + NestedClass1.class, + NestedClass2.class, + StaticNestedClass0.class, + StaticNestedClass1.class, + StaticNestedClass2.class }; + + for (Class clazz : classes) { + for (Constructor ctor : clazz.getConstructors()) { + System.out.println(ctor); + errors += checkGetParameterAnnotations(clazz, ctor); + errors += checkGetParametersGetAnnotation(clazz, ctor); + } + } + + if (errors > 0) + throw new RuntimeException(errors + " errors."); + return; + } + + private static int checkGetParameterAnnotations(Class clazz, + Constructor ctor) { + String annotationString = + Arrays.deepToString(ctor.getParameterAnnotations()); + String expectedString = + clazz.getAnnotation(ExpectedGetParameterAnnotations.class).value(); + + if (!Objects.equals(annotationString, expectedString)) { + System.err.println("Annotation mismatch on " + ctor + + "\n\tExpected:" + expectedString + + "\n\tActual: " + annotationString); + return 1; + } + return 0; + } + + private static int checkGetParametersGetAnnotation(Class clazz, + Constructor ctor) { + int errors = 0; + int i = 0; + ExpectedParameterAnnotations epa = + clazz.getAnnotation(ExpectedParameterAnnotations.class); + + for (Parameter param : ctor.getParameters() ) { + String annotationString = + Objects.toString(param.getAnnotation(MarkerAnnotation.class)); + String expectedString = epa.value()[i]; + + if (!Objects.equals(annotationString, expectedString)) { + System.err.println("Annotation mismatch on " + ctor + + " on param " + param + + "\n\tExpected:" + expectedString + + "\n\tActual: " + annotationString); + errors++; + } + i++; + } + return errors; + } + + @ExpectedGetParameterAnnotations("[[]]") + @ExpectedParameterAnnotations({"null"}) + public class NestedClass0 { + public NestedClass0() {} + } + + @ExpectedGetParameterAnnotations( + "[[], " + + "[@TestConstructorParameterAnnotations$MarkerAnnotation(value=1)]]") + @ExpectedParameterAnnotations({ + "null", + "@TestConstructorParameterAnnotations$MarkerAnnotation(value=1)"}) + public class NestedClass1 { + public NestedClass1(@MarkerAnnotation(1) int parameter) {} + } + + @ExpectedGetParameterAnnotations( + "[[], " + + "[@TestConstructorParameterAnnotations$MarkerAnnotation(value=2)], " + + "[]]") + @ExpectedParameterAnnotations({ + "null", + "@TestConstructorParameterAnnotations$MarkerAnnotation(value=2)", + "null"}) + public class NestedClass2 { + public NestedClass2(@MarkerAnnotation(2) int parameter1, + int parameter2) {} + } + + @ExpectedGetParameterAnnotations("[]") + @ExpectedParameterAnnotations({"null"}) + public static class StaticNestedClass0 { + public StaticNestedClass0() {} + } + + @ExpectedGetParameterAnnotations( + "[[@TestConstructorParameterAnnotations$MarkerAnnotation(value=1)]]") + @ExpectedParameterAnnotations({ + "@TestConstructorParameterAnnotations$MarkerAnnotation(value=1)"}) + public static class StaticNestedClass1 { + public StaticNestedClass1(@MarkerAnnotation(1) int parameter) {} + } + + @ExpectedGetParameterAnnotations( + "[[@TestConstructorParameterAnnotations$MarkerAnnotation(value=2)], " + + "[]]") + @ExpectedParameterAnnotations({ + "@TestConstructorParameterAnnotations$MarkerAnnotation(value=2)", + "null"}) + public static class StaticNestedClass2 { + public StaticNestedClass2(@MarkerAnnotation(2) int parameter1, + int parameter2) {} + } + + @Target(ElementType.PARAMETER) + @Retention(RetentionPolicy.RUNTIME) + @interface MarkerAnnotation { + int value(); + } + + /** + * String form of expected value of calling + * getParameterAnnotations on a constructor. + */ + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @interface ExpectedGetParameterAnnotations { + String value(); + } + + /** + * String form of expected value of calling + * getAnnotation(MarkerAnnotation.class) on each element of the + * result of getParameters() on a constructor. + */ + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @interface ExpectedParameterAnnotations { + String[] value(); + } +}