8044629: (reflect) Constructor.getAnnotatedReceiverType() returns wrong value
authorjfranck
Tue, 12 Aug 2014 10:16:58 +0200
changeset 25982 724303cf59ba
parent 25981 ae524ae51679
child 25983 12cac8cfce4a
8044629: (reflect) Constructor.getAnnotatedReceiverType() returns wrong value Summary: Update javadoc and align implementation with new spec Reviewed-by: psandoz
jdk/src/share/classes/java/lang/reflect/Constructor.java
jdk/src/share/classes/java/lang/reflect/Executable.java
jdk/test/java/lang/annotation/typeAnnotations/ConstructorReceiverTest.java
jdk/test/java/lang/annotation/typeAnnotations/GetAnnotatedReceiverType.java
jdk/test/java/lang/annotation/typeAnnotations/TestExecutableGetAnnotatedType.java
--- a/jdk/src/share/classes/java/lang/reflect/Constructor.java	Tue Aug 12 14:57:36 2014 +0100
+++ b/jdk/src/share/classes/java/lang/reflect/Constructor.java	Tue Aug 12 10:16:58 2014 +0200
@@ -544,15 +544,33 @@
      */
     @Override
     public AnnotatedType getAnnotatedReceiverType() {
-        if (getDeclaringClass().getEnclosingClass() == null)
-            return super.getAnnotatedReceiverType();
+        Class<?> thisDeclClass = getDeclaringClass();
+        Class<?> enclosingClass = thisDeclClass.getEnclosingClass();
+
+        if (enclosingClass == null) {
+            // A Constructor for a top-level class
+            return null;
+        }
 
+        Class<?> outerDeclaringClass = thisDeclClass.getDeclaringClass();
+        if (outerDeclaringClass == null) {
+            // A constructor for a local or anonymous class
+            return null;
+        }
+
+        // Either static nested or inner class
+        if (Modifier.isStatic(thisDeclClass.getModifiers())) {
+            // static nested
+            return null;
+        }
+
+        // A Constructor for an inner class
         return TypeAnnotationParser.buildAnnotatedType(getTypeAnnotationBytes0(),
                 sun.misc.SharedSecrets.getJavaLangAccess().
-                        getConstantPool(getDeclaringClass()),
+                    getConstantPool(thisDeclClass),
                 this,
-                getDeclaringClass(),
-                getDeclaringClass().getEnclosingClass(),
+                thisDeclClass,
+                enclosingClass,
                 TypeAnnotation.TypeAnnotationTarget.METHOD_RECEIVER);
     }
 }
--- a/jdk/src/share/classes/java/lang/reflect/Executable.java	Tue Aug 12 14:57:36 2014 +0100
+++ b/jdk/src/share/classes/java/lang/reflect/Executable.java	Tue Aug 12 10:16:58 2014 +0200
@@ -585,21 +585,24 @@
     /**
      * Returns an {@code AnnotatedType} object that represents the use of a
      * type to specify the receiver type of the method/constructor represented
-     * by this Executable object. The receiver type of a method/constructor is
-     * available only if the method/constructor has a <em>receiver
-     * parameter</em> (JLS 8.4.1).
+     * by this {@code Executable} object.
      *
-     * If this {@code Executable} object represents a constructor or instance
-     * method that does not have a receiver parameter, or has a receiver
-     * parameter with no annotations on its type, then the return value is an
-     * {@code AnnotatedType} object representing an element with no
+     * The receiver type of a method/constructor is available only if the
+     * method/constructor has a receiver parameter (JLS 8.4.1). If this {@code
+     * Executable} object <em>represents an instance method or represents a
+     * constructor of an inner member class</em>, and the
+     * method/constructor <em>either</em> has no receiver parameter or has a
+     * receiver parameter with no annotations on its type, then the return
+     * value is an {@code AnnotatedType} object representing an element with no
      * annotations.
      *
-     * If this {@code Executable} object represents a static method, then the
-     * return value is null.
+     * If this {@code Executable} object represents a static method or
+     * represents a constructor of a top level, static member, local, or
+     * anoymous class, then the return value is null.
      *
      * @return an object representing the receiver type of the method or
-     * constructor represented by this {@code Executable}
+     * constructor represented by this {@code Executable} or {@code null} if
+     * this {@code Executable} can not have a receiver parameter
      */
     public AnnotatedType getAnnotatedReceiverType() {
         if (Modifier.isStatic(this.getModifiers()))
--- a/jdk/test/java/lang/annotation/typeAnnotations/ConstructorReceiverTest.java	Tue Aug 12 14:57:36 2014 +0100
+++ b/jdk/test/java/lang/annotation/typeAnnotations/ConstructorReceiverTest.java	Tue Aug 12 10:16:58 2014 +0200
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @bug 8023651
+ * @bug 8023651 8044629
  * @summary Test that the receiver annotations and the return annotations of
  *          constructors behave correctly.
  * @run testng ConstructorReceiverTest
@@ -38,11 +38,16 @@
 import static org.testng.Assert.*;
 
 public class ConstructorReceiverTest {
+    public static final Integer EMPTY_ANNOTATED_TYPE =  Integer.valueOf(-1);
+
     // Format is {
     //   { Class to get ctor for,
     //       ctor param class,
     //       value of anno of return type,
-    //       value of anno for receiver or null if there should be no receiver anno
+    //       value of anno for receiver,
+    //              or null if there should be no receiver,
+    //              or EMPTY_ANNOTATED_TYPE of there should be a receiver but
+    //              no annotation
     //    },
     //    ...
     // }
@@ -51,13 +56,15 @@
         { ConstructorReceiverTest.Middle.class, ConstructorReceiverTest.class, Integer.valueOf(10), Integer.valueOf(15) },
         { ConstructorReceiverTest.Middle.Inner.class, ConstructorReceiverTest.Middle.class, Integer.valueOf(100), Integer.valueOf(150) },
         { ConstructorReceiverTest.Middle.Inner.Innermost.class, ConstructorReceiverTest.Middle.Inner.class, Integer.valueOf(1000), Integer.valueOf(1500) },
-        { ConstructorReceiverTest.Middle.InnerNoReceiver.class, ConstructorReceiverTest.Middle.class, Integer.valueOf(300), null },
+        { ConstructorReceiverTest.Middle.InnerNoReceiver.class, ConstructorReceiverTest.Middle.class, Integer.valueOf(300), EMPTY_ANNOTATED_TYPE },
         { ConstructorReceiverTest.Nested.class, null, Integer.valueOf(20), null },
         { ConstructorReceiverTest.Nested.NestedMiddle.class, ConstructorReceiverTest.Nested.class, Integer.valueOf(200), Integer.valueOf(250)},
         { ConstructorReceiverTest.Nested.NestedMiddle.NestedInner.class, ConstructorReceiverTest.Nested.NestedMiddle.class, Integer.valueOf(2000), Integer.valueOf(2500)},
-        { ConstructorReceiverTest.Nested.NestedMiddle.NestedInnerNoReceiver.class, ConstructorReceiverTest.Nested.NestedMiddle.class, Integer.valueOf(4000), null},
+        { ConstructorReceiverTest.Nested.NestedMiddle.NestedInnerNoReceiver.class, ConstructorReceiverTest.Nested.NestedMiddle.class, Integer.valueOf(4000), EMPTY_ANNOTATED_TYPE},
+        { ConstructorReceiverTest.Nested.NestedMiddle.SecondNestedInnerNoReceiver.class, ConstructorReceiverTest.Nested.NestedMiddle.class, Integer.valueOf(5000), EMPTY_ANNOTATED_TYPE},
     };
 
+
     @DataProvider
     public Object[][] data() { return TESTS; }
 
@@ -71,14 +78,27 @@
             c = toTest.getDeclaredConstructor(ctorParamType);
 
         AnnotatedType annotatedReceiverType = c.getAnnotatedReceiverType();
-        Annotation[] receiverAnnotations = annotatedReceiverType.getAnnotations();
 
+        // Some Constructors doesn't conceptually have a receiver, they should return null
         if (receiverVal == null) {
-            assertEquals(receiverAnnotations.length, 0, Arrays.asList(receiverAnnotations).toString() +
-                    " should be empty. Looking at 'length': ");
+            assertNull(annotatedReceiverType, "getAnnotatedReciverType  should return null for Constructor: " + c);
             return;
         }
 
+        // check that getType() matches the receiver
+        assertEquals(annotatedReceiverType.getType(),
+                ctorParamType,
+                "getType() doesn't match receiver type: " + ctorParamType);
+
+        Annotation[] receiverAnnotations = annotatedReceiverType.getAnnotations();
+
+        // Some Constructors have no annotations on but in theory can have a receiver
+        if (receiverVal.equals(EMPTY_ANNOTATED_TYPE)) {
+            assertEquals(receiverAnnotations.length, 0, "expecting an empty annotated type for: " + c);
+            return;
+        }
+
+        // The rest should have annotations
         assertEquals(receiverAnnotations.length, 1, "expecting a 1 element array. Looking at 'length': ");
         assertEquals(((Annot)receiverAnnotations[0]).value(), receiverVal.intValue(), " wrong annotation found. Found " +
                 receiverAnnotations[0] +
@@ -136,6 +156,10 @@
             class NestedInnerNoReceiver {
                 @Annot(4000) public NestedInnerNoReceiver() {}
             }
+
+            class SecondNestedInnerNoReceiver {
+                @Annot(5000) public SecondNestedInnerNoReceiver(NestedMiddle NestedMiddle.this) {}
+            }
         }
     }
 
--- a/jdk/test/java/lang/annotation/typeAnnotations/GetAnnotatedReceiverType.java	Tue Aug 12 14:57:36 2014 +0100
+++ b/jdk/test/java/lang/annotation/typeAnnotations/GetAnnotatedReceiverType.java	Tue Aug 12 10:16:58 2014 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2014 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
@@ -23,10 +23,11 @@
 
 /*
  * @test
- * @bug 8024915
+ * @bug 8024915 8044629
  */
 
 import java.lang.reflect.AnnotatedType;
+import java.lang.reflect.Executable;
 import java.util.Arrays;
 
 public class GetAnnotatedReceiverType {
@@ -42,41 +43,115 @@
         public Inner1(GetAnnotatedReceiverType GetAnnotatedReceiverType.this) {}
     }
 
+    public static class Nested {
+        public Nested() {}
+
+        public class NestedInner {
+            public NestedInner() { }
+
+            public Class<?> getLocalClass () {
+                class NestedInnerLocal { public NestedInnerLocal() {} }
+                return NestedInnerLocal.class;
+            }
+
+            public Class<?> getAnonymousClass() {
+                return new Object() {}.getClass();
+            }
+        }
+    }
+
+    public class Inner2 {
+        public Inner2() { }
+
+        public class Inner3 {
+            public Inner3() { }
+
+            public Class<?> getLocalClass () {
+                class InnerLocal { public InnerLocal() {} }
+                return InnerLocal.class;
+            }
+
+            public Class<?> getAnonymousClass() {
+                return new Object() {}.getClass();
+            }
+        }
+
+        public Class<?> getLocalClass () {
+            class InnerLocal { public InnerLocal() {} }
+                return InnerLocal.class;
+        }
+
+        public Class<?> getAnonymousClass() {
+            return new Object() {}.getClass();
+        }
+    }
+
     private static int failures = 0;
     private static int tests = 0;
 
     public static void main(String[] args) throws NoSuchMethodException {
-        checkEmptyAT(GetAnnotatedReceiverType.class.getMethod("method").getAnnotatedReceiverType(),
+        checkEmptyAT(GetAnnotatedReceiverType.class.getMethod("method"),
                 "getAnnotatedReceiverType for \"method\" should return an empty AnnotatedType");
-        checkEmptyAT(Inner0.class.getConstructor(GetAnnotatedReceiverType.class).getAnnotatedReceiverType(),
+        checkEmptyAT(Inner0.class.getConstructor(GetAnnotatedReceiverType.class),
                 "getAnnotatedReceiverType for a ctor without a \"this\" should return an empty AnnotatedType");
 
-        checkEmptyAT(GetAnnotatedReceiverType.class.getMethod("method0").getAnnotatedReceiverType(),
+        checkEmptyAT(GetAnnotatedReceiverType.class.getMethod("method0"),
                 "getAnnotatedReceiverType for \"method0\" should return an empty AnnotatedType");
-        checkEmptyAT(Inner1.class.getConstructor(GetAnnotatedReceiverType.class).getAnnotatedReceiverType(),
+        checkEmptyAT(Inner1.class.getConstructor(GetAnnotatedReceiverType.class),
                 "getAnnotatedReceiverType for a ctor with a \"this\" should return an empty AnnotatedType");
 
-        checkNull(GetAnnotatedReceiverType.class.getMethod("method4").getAnnotatedReceiverType(),
+        checkNull(GetAnnotatedReceiverType.class.getMethod("method4"),
                 "getAnnotatedReceiverType() on a static method should return null");
 
+        // More nested, inner, local and anonymous classes
+        Nested nested = new Nested();
+        Nested.NestedInner instance = nested.new NestedInner();
+        checkNull(nested.getClass().getConstructors()[0],
+                "getAnnotatedReceiverType() on a constructor for a static class should return null");
+        checkEmptyAT(instance.getClass().getConstructors()[0],
+                "getAnnotatedReceiverType for a ctor without a \"this\" should return an empty AnnotatedType");
+        checkNull(instance.getLocalClass().getConstructors()[0],
+                "getAnnotatedReceiverType() on a constructor for a local class should return null");
+        checkNull(instance.getAnonymousClass().getDeclaredConstructors()[0],
+                "getAnnotatedReceiverType() on a constructor for an anonymous class should return null");
+
+        GetAnnotatedReceiverType outer = new GetAnnotatedReceiverType();
+        Inner2 instance2 = outer.new Inner2();
+        checkEmptyAT(instance2.getClass().getConstructors()[0],
+                "getAnnotatedReceiverType for a ctor without a \"this\" should return an empty AnnotatedType");
+        checkNull(instance2.getLocalClass().getConstructors()[0],
+                "getAnnotatedReceiverType() on a constructor for a local class should return null");
+        checkNull(instance2.getAnonymousClass().getDeclaredConstructors()[0],
+                "getAnnotatedReceiverType() on a constructor for an anonymous class should return null");
+
+        Inner2.Inner3 instance3 = instance2.new Inner3();
+        checkEmptyAT(instance3.getClass().getConstructors()[0],
+                "getAnnotatedReceiverType for a ctor without a \"this\" should return an empty AnnotatedType");
+        checkNull(instance3.getLocalClass().getConstructors()[0],
+                "getAnnotatedReceiverType() on a constructor for a local class should return null");
+        checkNull(instance3.getAnonymousClass().getDeclaredConstructors()[0],
+                "getAnnotatedReceiverType() on a constructor for an anonymous class should return null");
+
         if (failures != 0)
             throw new RuntimeException("Test failed, see log for details");
-        else if (tests != 5)
+        else if (tests != 15)
             throw new RuntimeException("Not all cases ran, failing");
     }
 
-    private static void checkNull(Object o, String msg) {
-        if (o != null) {
+    private static void checkNull(Executable e, String msg) {
+        AnnotatedType a = e.getAnnotatedReceiverType();
+        if (a != null) {
             failures++;
-            System.err.println(msg);
+            System.err.println(msg + ": " + e);
         }
         tests++;
     }
 
-    private static void checkEmptyAT(AnnotatedType a, String msg) {
+    private static void checkEmptyAT(Executable e, String msg) {
+        AnnotatedType a = e.getAnnotatedReceiverType();
         if (a.getAnnotations().length != 0) {
             failures++;
-            System.err.print(msg);
+            System.err.print(msg + ": " + e);
         }
         tests++;
     }
--- a/jdk/test/java/lang/annotation/typeAnnotations/TestExecutableGetAnnotatedType.java	Tue Aug 12 14:57:36 2014 +0100
+++ b/jdk/test/java/lang/annotation/typeAnnotations/TestExecutableGetAnnotatedType.java	Tue Aug 12 10:16:58 2014 +0200
@@ -73,13 +73,11 @@
         testParameters(e.getParameters());
     }
 
-    // should test constructors as well, see JDK-8044629
     @Test(dataProvider = "genericMethodData")
     public void testGenericReceiverType(Executable e) throws Exception {
         testReceiverType0(e);
     }
 
-    // should test constructors as well, see JDK-8044629
     @Test(dataProvider = "methodData")
     public void testReceiverType(Executable e) throws Exception {
         testReceiverType0(e);