1 /*
   2  * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 
  25 /*
  26  * @test
  27  * @summary test reflection on inline types
  28  * @compile -XDallowWithFieldOperator Point.java Line.java NonFlattenValue.java
  29  * @run main/othervm -XX:+EnableValhalla Reflection
  30  */
  31 
  32 import java.lang.reflect.Array;
  33 import java.lang.reflect.Constructor;
  34 import java.lang.reflect.Field;
  35 import java.lang.reflect.InaccessibleObjectException;
  36 import java.lang.reflect.Method;
  37 import java.lang.reflect.Modifier;
  38 import java.util.Arrays;
  39 import java.util.List;
  40 import java.util.stream.Collectors;
  41 
  42 public class Reflection {
  43     public static void main(String... args) throws Exception {
  44         testPointClass();
  45         testLineClass();
  46         testNonFlattenValue();
  47         testMirrors();
  48         testClassName();
  49     }
  50 
  51     static void testPointClass() throws Exception  {
  52         Point o = Point.makePoint(10, 20);
  53         Reflection test = new Reflection(Point.class, "Point", o);
  54         test.newInstance();
  55         test.constructor();
  56         test.constructors("public Point(int,int)", "Point()");
  57         test.accessFieldX(o.x);
  58         test.setAccessible();
  59         test.trySetAccessible();
  60         test.staticField();
  61         test.initFactoryNotMethods();
  62     }
  63 
  64     static void testLineClass() throws Exception {
  65         Line l = Line.makeLine(10, 20, 30, 40);
  66         Reflection test = new Reflection(Line.class, "Line", l);
  67         test.checkField("public final Point Line.p1", "p1", Point.class);
  68         test.checkField("public final Point Line.p2", "p2", Point.class);
  69         test.checkMethod("public Point Line.p1()",           "p1", Point.class);
  70         test.checkMethod("public Point Line.p2()",           "p2", Point.class);
  71         test.initFactoryNotMethods();
  72         test.constructors("Line()");
  73     }
  74 
  75     static void testNonFlattenValue() throws Exception {
  76         NonFlattenValue nfv = NonFlattenValue.make(10, 20);
  77         Reflection test = new Reflection(NonFlattenValue.class, "NonFlattenValue", nfv);
  78         test.checkField("final Point? NonFlattenValue.nfp", "nfp", Point.class.asNullableType());
  79         test.checkMethod("public Point NonFlattenValue.pointValue()", "pointValue", Point.class);
  80         test.checkMethod("public Point? NonFlattenValue.point()", "point", Point.class.asNullableType());
  81         test.checkMethod("public boolean NonFlattenValue.has(Point,Point?)", "has", boolean.class, Point.class, Point.class.asNullableType());
  82         test.initFactoryNotMethods();
  83         test.constructors("NonFlattenValue()");
  84 
  85     }
  86 
  87     /*
  88      * Tests reflection APIs with the primary and nullable mirror
  89      */
  90     static void testMirrors() throws Exception {
  91         Class<?> primary = Point.class;
  92         Class<?> nullable = Point.class.asNullableType();
  93 
  94         assertEquals(primary, Point.class);
  95         assertTrue(primary.isInlineClass());
  96         assertTrue(nullable.isInlineClass());
  97 
  98         Point o = Point.makePoint(10, 20);
  99         assertTrue(primary.isInstance(o));
 100         assertTrue(nullable.isInstance(o));
 101 
 102         // V <: V? and V <: Object
 103         assertTrue(nullable.isAssignableFrom(primary));
 104         assertTrue(Object.class.isAssignableFrom(primary));
 105         assertFalse(primary.isAssignableFrom(nullable));
 106         assertTrue(Object.class.isAssignableFrom(nullable));
 107 
 108         assertEquals(primary, primary.asSubclass(nullable));
 109         try {
 110             Class<?> c = nullable.asSubclass(primary);
 111             assertTrue(false);
 112         } catch (ClassCastException e) { }
 113     }
 114 
 115     static void testClassName() {
 116         assertEquals(Point.class.getName(), "Point");
 117         assertEquals(Point.class.asNullableType().getName(), "Point");
 118         assertEquals(Line.class.getName(), "Line");
 119         assertEquals((new Point[0]).getClass().getName(), "[QPoint;");
 120         assertEquals((new Point?[0][0]).getClass().getName(), "[[LPoint;");
 121     }
 122 
 123     private final Class<?> c;
 124     private final Constructor<?> ctor;
 125     private final Object o;
 126     Reflection(Class<?> type, String cn, Object o) throws Exception {
 127         this.c = Class.forName(cn);
 128         if (!c.isInlineClass() || c != type) {
 129             throw new RuntimeException(cn + " is not an inline class");
 130         }
 131 
 132         // V.class, Class.forName, and the type of the object return the primary mirror
 133         assertEquals(type, o.getClass());
 134         assertEquals(type, c.asPrimaryType());
 135         assertEquals(c, c.asPrimaryType());
 136 
 137         this.ctor = c.getDeclaredConstructor();
 138         this.o = o;
 139 
 140 
 141         // test the primary mirror and secondary mirror
 142         testMirrors(this.c);
 143         // test array of Q-type and L-type
 144         testArray(c.asPrimaryType());
 145         testArray(c.asNullableType());
 146     }
 147 
 148     private static void testMirrors(Class<?> c) {
 149         Class<?> inlineType = c.asPrimaryType();
 150         Class<?> nullableType = c.asNullableType();
 151 
 152         assertTrue(inlineType != null);
 153         assertEquals(nullableType.getTypeName(), c.getTypeName() + "?");
 154 
 155         assertEquals(nullableType.getName(), inlineType.getName());
 156         assertEquals(nullableType.getTypeName(), inlineType.getTypeName() + "?");
 157         assertEquals(inlineType.asNullableType(), nullableType);
 158         assertEquals(nullableType.asPrimaryType(), inlineType);
 159     }
 160 
 161     void testArray(Class<?> elementType) {
 162         Object[] array = (Object[])Array.newInstance(elementType, 1);
 163         Class<?> arrayType = array.getClass();
 164         assertTrue(arrayType.isArray());
 165         Class<?> componentType = arrayType.getComponentType();
 166         assertTrue(componentType.isInlineClass());
 167         assertEquals(componentType, elementType);
 168         // Array is a reference type
 169         assertEquals(arrayType.asNullableType(), arrayType);
 170         if (array[0] == null) {
 171             System.out.println("array[0] = null");
 172         } else {
 173             System.out.println("array[0] = " + array[0]);
 174         }
 175     }
 176 
 177     void accessFieldX(int x) throws Exception {
 178         Field field = c.getField("x");
 179         if (field.getInt(o) != x) {
 180             throw new RuntimeException("Unexpected Point.x value: " +  field.getInt(o));
 181         }
 182 
 183         try {
 184             field.setInt(o, 100);
 185             throw new RuntimeException("IllegalAccessException not thrown");
 186         } catch (IllegalAccessException e) {}
 187     }
 188 
 189     @SuppressWarnings("deprecation")
 190     void newInstance() throws Exception {
 191         Object o = c.newInstance();
 192         assertEquals(o.getClass(), c);
 193     }
 194 
 195     void constructor() throws Exception {
 196         Object o = ctor.newInstance();
 197         assertEquals(o.getClass(), c);
 198     }
 199 
 200     // Check that the class has the expected Constructors
 201     void constructors(String... expected) throws Exception {
 202         Constructor<? extends Object>[] cons = c.getDeclaredConstructors();
 203         List<String> declaredSig = Arrays.stream(cons).map( Constructor::toString).collect(Collectors.toList());
 204         List<String> expectedSig = List.of(expected);
 205         boolean ok = expectedSig.equals(declaredSig);
 206         if (!ok) {
 207             System.out.printf("expected: %s%n", expectedSig);
 208             System.out.printf("declared: %s%n", declaredSig);
 209             assertTrue(ok);
 210         }
 211     }
 212 
 213     void setAccessible() throws Exception {
 214         ctor.setAccessible(true);
 215         Field field = c.getField("x");
 216         try {
 217             field.setAccessible(true);
 218             throw new RuntimeException("InaccessibleObjectException not thrown");
 219         } catch (InaccessibleObjectException e) {
 220             System.out.println("as expected: " + e.toString());
 221         }
 222     }
 223 
 224     void trySetAccessible() throws Exception {
 225         ctor.trySetAccessible();
 226         Field field = c.getField("x");
 227         if (field.trySetAccessible()) {
 228             throw new RuntimeException("trySetAccessible should not succeed");
 229         }
 230     }
 231 
 232     void staticField() throws Exception {
 233         Field f = c.getDeclaredField("STATIC_FIELD");
 234         if (f.trySetAccessible()) {
 235             throw new RuntimeException("trySetAccessible should not succeed");
 236         }
 237         try {
 238             f.setAccessible(true);
 239             throw new RuntimeException("IllegalAccessException not thrown");
 240         } catch (InaccessibleObjectException e) { }
 241     }
 242 
 243     void checkField(String source, String name, Class<?> type) throws Exception {
 244         Field f = c.getDeclaredField(name);
 245         assertEquals(f.getType(), type);
 246         assertEquals(f.toString(), source);
 247     }
 248 
 249     void checkMethod(String source, String name, Class<?> returnType, Class<?>... params) throws Exception {
 250         Method m = c.getDeclaredMethod(name, params);
 251         assertEquals(m.toString(), source);
 252     }
 253 
 254     // Check that the class does not have a static method with the name <init>
 255     void initFactoryNotMethods() throws Exception {
 256         Method[] methods = c.getDeclaredMethods();
 257         for (Method m : methods) {
 258             if (Modifier.isStatic(m.getModifiers())) {
 259                 assertFalse(m.getName().equals("<init>"));
 260             }
 261         }
 262     }
 263 
 264     static void assertEquals(Object o1, Object o2) {
 265         if (o1 == o2 || o1.equals(o2))
 266             return;
 267 
 268         throw new AssertionError(o1 + " != " + o2);
 269     }
 270 
 271     static void assertTrue(boolean value) {
 272         if (!value)
 273             throw new AssertionError("expected true");
 274     }
 275 
 276     static void assertFalse(boolean value) {
 277         if (value)
 278             throw new AssertionError("expected false");
 279     }
 280 }