1 /*
   2  * Copyright (c) 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 MethodHandles on value types
  28  * @build Point Line MutablePath
  29  * @run testng/othervm -XX:+EnableValhalla MethodHandleTest
  30  */
  31 
  32 import java.lang.invoke.*;
  33 import java.lang.reflect.Field;
  34 import java.util.*;
  35 
  36 import org.testng.annotations.BeforeTest;
  37 import org.testng.annotations.DataProvider;
  38 import org.testng.annotations.Test;
  39 import static org.testng.Assert.*;
  40 
  41 public class MethodHandleTest {
  42     private static final Point P = Point.makePoint(10, 20);
  43     private static final Line L = Line.makeLine(10, 20, 30, 40);
  44     private static final MutablePath PATH = MutablePath.makePath(10, 20, 30, 40);
  45 
  46     @Test
  47     public static void testPointClass() throws Throwable  {
  48         MethodHandleTest test = new MethodHandleTest("Point", P, "x", "y");
  49         test.run();
  50     }
  51 
  52     @Test
  53     public static void testLineClass() throws Throwable {
  54         MethodHandleTest test = new MethodHandleTest("Line", L, "p1", "p2");
  55         test.run();
  56     }
  57 
  58     @Test
  59     public static void testMutablePath() throws Throwable {
  60         MethodHandleTest test = new MethodHandleTest("MutablePath", PATH, "p1", "p2");
  61         test.run();
  62 
  63         // set the mutable fields
  64         Point p = Point.makePoint(100, 200);
  65         test.setFlattenedField(p);
  66     }
  67 
  68     @Test
  69     public static void testMixedValues() throws Throwable {
  70         MixedValues mv = new MixedValues(P, L, PATH, "mixed", "types");
  71         MethodHandleTest test = new MethodHandleTest("MethodHandleTest$MixedValues", mv, "p", "l", "mutablePath", "list");
  72         test.run();
  73     }
  74 
  75     @Test
  76     public static void testArrayElementSetterAndGetter() throws Throwable {
  77         testArray(Point[].class, P);
  78         testArray(Line[].class, L);
  79         testArray(MutablePath[].class, PATH);
  80     }
  81 
  82     static void testArray(Class<?> c, Object o) throws Throwable {
  83         MethodHandle setter = MethodHandles.arrayElementSetter(c);
  84         MethodHandle getter = MethodHandles.arrayElementGetter(c);
  85         MethodHandle ctor = MethodHandles.arrayConstructor(c);
  86         int size = 5;
  87         Object[] array = (Object[])ctor.invoke(size);
  88         for (int i=0; i < size; i++) {
  89             setter.invoke(array, i, o);
  90         }
  91         for (int i=0; i < size; i++) {
  92             Object v = (Object)getter.invoke(array, i);
  93             assertEquals(v, o);
  94         }
  95 
  96         // set an array element to null
  97         /*
  98         Class<?> elementType = c.getComponentType();
  99         try {
 100             Object v = (Object)setter.invoke(array, 0, null);
 101             assertFalse(elementType.isValue(), "should fail to set a value array element to null");
 102         } catch (NullPointerException e) {
 103             assertTrue(elementType.isValue(), "should only fail to set a value array element to null");
 104         }
 105         */
 106     }
 107 
 108     private final Class<?> c;
 109     private final Object o;
 110     private final List<String> names;
 111     public MethodHandleTest(String cn, Object o, String... fields) throws Exception {
 112         this.c = Class.forName(cn);
 113         this.o = o;
 114         this.names = List.of(fields);
 115     }
 116 
 117     public void run() throws Throwable {
 118         for (String name : names) {
 119             Field f = c.getDeclaredField(name);
 120             unreflectField(f);
 121             findGetter(f);
 122             varHandle(f);
 123             // ensureNonNullable(f);
 124         }
 125     }
 126 
 127     public List<String> names() {
 128         return names;
 129     }
 130 
 131     void findGetter(Field f) throws Throwable {
 132         MethodHandle mh = MethodHandles.lookup().findGetter(c, f.getName(), f.getType());
 133         Object value = mh.invoke(o);
 134     }
 135 
 136     void varHandle(Field f) throws Throwable {
 137         VarHandle vh = MethodHandles.lookup().findVarHandle(c, f.getName(), f.getType());
 138         Object value = vh.get(o);
 139     }
 140 
 141     void unreflectField(Field f) throws Throwable {
 142         MethodHandle mh = MethodHandles.lookup().unreflectGetter(f);
 143         Object value = mh.invoke(o);
 144     }
 145 
 146     void setFlattenedField(Object value) throws Exception {
 147         for (String name : names) {
 148             Field f = c.getDeclaredField(name);
 149             assertTrue(isFlattened(f));
 150             f.set(o, value);
 151             Object nv = f.get(o);
 152             assertEquals(nv, value);
 153         }
 154     }
 155 
 156     void ensureNonNullable(Field f) throws Throwable {
 157         boolean nullable = !f.getType().isValue();
 158         try {
 159             f.set(o, null);
 160             assertTrue(nullable, f + " cannot be set to null");
 161         } catch (NullPointerException e) {
 162             assertFalse(nullable, f + " should allow be set to null");
 163         } catch (IllegalAccessException e) {
 164             assertTrue(c.isValue());
 165         }
 166         try {
 167             MethodHandle mh = MethodHandles.lookup().findSetter(c, f.getName(), f.getType());
 168             mh.invoke(o, null);
 169             assertTrue(nullable, f + " cannot be set to null");
 170         } catch (NullPointerException e) {
 171             assertFalse(nullable, f + " should allow be set to null");
 172         } catch (IllegalAccessException e) {
 173             assertTrue(c.isValue());
 174         }
 175         try {
 176             VarHandle vh = MethodHandles.lookup().findVarHandle(c, f.getName(), f.getType());
 177             vh.set(o, null);
 178             assertTrue(nullable, f + " cannot be set to null");
 179         } catch (NullPointerException e) {
 180             assertFalse(nullable, f + " should allow be set to null");
 181         } catch (UnsupportedOperationException e) {  // TODO: this should be IAE
 182             assertTrue(c.isValue());
 183         }
 184     }
 185 
 186 
 187     boolean isFlattened(Field f) {
 188         return (f.getModifiers() & 0x00008000) == 0x00008000;
 189     }
 190 
 191     static class MixedValues {
 192         Point p;
 193         Line l;
 194         MutablePath mutablePath;
 195         List<String> list;
 196 
 197         public MixedValues(Point p, Line l, MutablePath path, String... names) {
 198             this.p = p;
 199             this.l = l;
 200             this.mutablePath = path;
 201             this.list = List.of(names);
 202         }
 203     }
 204 
 205 }