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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 import java.lang.compiler.Extractor;
  27 import java.lang.compiler.SwitchBootstraps;
  28 import java.lang.invoke.CallSite;
  29 import java.lang.invoke.MethodHandle;
  30 import java.lang.invoke.MethodHandles;
  31 import java.lang.invoke.MethodType;
  32 
  33 import org.testng.annotations.Test;
  34 
  35 import static java.lang.invoke.MethodHandleInfo.REF_newInvokeSpecial;
  36 import static org.testng.Assert.assertEquals;
  37 
  38 @Test
  39 /**
  40  * @test
  41  * @run testng RecordTest
  42  * @summary End-to-end test for record patterns
  43  */
  44 public class RecordTest {
  45     record R(int a, String b, double c);
  46     record RR(R r1, R R2);
  47 
  48     private Extractor recordExtractor(Class<?> recordClass,
  49                                       Class<?>... paramTypes) throws Throwable {
  50         return Extractor.findExtractor(MethodHandles.lookup(), "_", Extractor.class,
  51                                        recordClass, MethodType.methodType(void.class, paramTypes), recordClass.getName(), REF_newInvokeSpecial);
  52     }
  53 
  54     public void testRecord() throws Throwable {
  55         R r = new R(1, "two", 3.14d);
  56         Extractor rExtract = recordExtractor(R.class, int.class, String.class, double.class);
  57 
  58         MethodHandle tryExtract = Extractor.extractorTryMatch(MethodHandles.lookup(), "_", MethodHandle.class, rExtract);
  59         MethodHandle a = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 0);
  60         MethodHandle b = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 1);
  61         MethodHandle c = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 2);
  62 
  63         Object o = tryExtract.invoke(r);
  64         assertEquals(1, a.invoke(o));
  65         assertEquals("two", b.invoke(o));
  66         assertEquals(3.14d, c.invoke(o));
  67     }
  68 
  69     public void testFakeNested() throws Throwable {
  70         R r1 = new R(1, "two", 3.14d);
  71         R r2 = new R(2, "four", 6.0d);
  72         RR rr = new RR(r1, r2);
  73 
  74         Extractor rExtract = recordExtractor(R.class, int.class, String.class, double.class);
  75         Extractor rrExtract = recordExtractor(RR.class, R.class, R.class);
  76 
  77         MethodHandle tryExtractR = Extractor.extractorTryMatch(MethodHandles.lookup(), "_", MethodHandle.class, rExtract);
  78         MethodHandle ra = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 0);
  79         MethodHandle rb = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 1);
  80         MethodHandle rc = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 2);
  81 
  82         MethodHandle tryExtractRr = Extractor.extractorTryMatch(MethodHandles.lookup(), "_", MethodHandle.class, rrExtract);
  83         MethodHandle r1c = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rrExtract, 0);
  84         MethodHandle r2c = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rrExtract, 1);
  85 
  86         Object o = tryExtractRr.invoke(rr);
  87         R o1 = (R) r1c.invoke(o);
  88         R o2 = (R) r2c.invoke(o);
  89 
  90         assertEquals(1, ra.invoke(o1));
  91         assertEquals("two", rb.invoke(o1));
  92         assertEquals(3.14d, rc.invoke(o1));
  93 
  94         assertEquals(2, ra.invoke(o2));
  95         assertEquals("four", rb.invoke(o2));
  96         assertEquals(6.0d, rc.invoke(o2));
  97     }
  98 
  99     public void testNested() throws Throwable {
 100         Extractor rExtract = recordExtractor(R.class, int.class, String.class, double.class);
 101         Extractor rrExtract = recordExtractor(RR.class, R.class, R.class);
 102 
 103         Extractor e = Extractor.ofNested(rrExtract, rExtract);
 104 
 105         R r1 = new R(1, "two", 3.14d);
 106         R r2 = new R(2, "four", 6.0d);
 107         RR rr = new RR(r1, r2);
 108 
 109         Object o = e.tryMatch().invoke(rr);
 110 
 111         assertEquals(e.component(0).invoke(o), new R(1, "two", 3.14d));
 112         assertEquals(e.component(1).invoke(o), new R(2, "four", 6.0d));
 113         assertEquals(e.component(2).invoke(o), 1);
 114         assertEquals(e.component(3).invoke(o), "two");
 115         assertEquals(e.component(4).invoke(o), 3.14d);
 116 
 117         Extractor ee = Extractor.ofNested(rrExtract, rExtract, rExtract);
 118         o = ee.tryMatch().invoke(rr);
 119 
 120         assertEquals(ee.component(0).invoke(o), new R(1, "two", 3.14d));
 121         assertEquals(ee.component(1).invoke(o), new R(2, "four", 6.0d));
 122         assertEquals(ee.component(2).invoke(o), 1);
 123         assertEquals(ee.component(3).invoke(o), "two");
 124         assertEquals(ee.component(4).invoke(o), 3.14d);
 125         assertEquals(ee.component(5).invoke(o), 2);
 126         assertEquals(ee.component(6).invoke(o), "four");
 127         assertEquals(ee.component(7).invoke(o), 6.0d);
 128     }
 129 
 130     record A(int a);
 131     record B(int a, int b);
 132     record S(String s);
 133     record T(String s, String t);
 134     record U();
 135 
 136     private Object component(Extractor e, int num, Object carrier) throws Throwable {
 137         return Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class,
 138                                             e, num).invoke(carrier);
 139     }
 140 
 141     public void testRecordSwitch() throws Throwable {
 142         Extractor[] extractors = {
 143                 recordExtractor(A.class, int.class),
 144                 recordExtractor(B.class, int.class, int.class),
 145                 recordExtractor(S.class, String.class),
 146                 recordExtractor(T.class, String.class, String.class),
 147                 recordExtractor(U.class)
 148         };
 149 
 150         Object[] exemplars = {
 151                 new A(1),
 152                 new B(2, 3),
 153                 new S("four"),
 154                 new T("five", "six"),
 155                 new U()
 156         };
 157 
 158         CallSite cs = SwitchBootstraps.patternSwitch(MethodHandles.lookup(), "_",
 159                                                      MethodType.methodType(SwitchBootstraps.PatternSwitchResult.class, Object.class),
 160                                                      extractors);
 161         MethodHandle mh = cs.dynamicInvoker();
 162         for (int i = 0; i < exemplars.length; i++) {
 163             Object exemplar = exemplars[i];
 164             SwitchBootstraps.PatternSwitchResult result = (SwitchBootstraps.PatternSwitchResult) mh.invoke(exemplar);
 165             assertEquals(result.index, i);
 166             switch (result.index) {
 167                 case 0:
 168                     assertEquals(component(extractors[i], 0, result.carrier), 1);
 169                     break;
 170                 case 1:
 171                     assertEquals(component(extractors[i], 0, result.carrier), 2);
 172                     assertEquals(component(extractors[i], 1, result.carrier), 3);
 173                     break;
 174                 case 2:
 175                     assertEquals(component(extractors[i], 0, result.carrier), "four");
 176                     break;
 177                 case 3:
 178                     assertEquals(component(extractors[i], 0, result.carrier), "five");
 179                     assertEquals(component(extractors[i], 1, result.carrier), "six");
 180                     break;
 181             };
 182 
 183             result = (SwitchBootstraps.PatternSwitchResult) mh.invoke(null);
 184             assertEquals(result.index, -1);
 185 
 186             result = (SwitchBootstraps.PatternSwitchResult) mh.invoke("foo");
 187             assertEquals(result.index, 5);
 188         }
 189     }
 190 
 191     record Box(Object o1);
 192 
 193     public void testNestedRecord() throws Throwable {
 194         Extractor boxA = Extractor.ofNested(recordExtractor(Box.class, Object.class),
 195                                             recordExtractor(A.class, int.class));
 196         Extractor boxB = Extractor.ofNested(recordExtractor(Box.class, Object.class),
 197                                             recordExtractor(B.class, int.class, int.class));
 198 
 199         CallSite cs = SwitchBootstraps.patternSwitch(MethodHandles.lookup(), "_",
 200                                                      MethodType.methodType(SwitchBootstraps.PatternSwitchResult.class, Object.class),
 201                                                      boxA, boxB);
 202         MethodHandle mh = cs.dynamicInvoker();
 203 
 204         assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke(new Box(new A(1)))).index, 0);
 205         assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke(new Box(new B(2, 3)))).index, 1);
 206         assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke(new Box("foo"))).index, 2);
 207         assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke(new Box(null))).index, 2);
 208         assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke("foo")).index, 2);
 209         assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke(null)).index, -1);
 210     }
 211 
 212     record RString(String i) { }
 213     record RObject(Object i) { }
 214     record Rint(int i) { }
 215 
 216 
 217     public void testNestedWithConstant() throws Throwable {
 218         Extractor rb = recordExtractor(Box.class, Object.class);
 219         Extractor rs = recordExtractor(RString.class, String.class);
 220         Extractor ro = recordExtractor(RObject.class, Object.class);
 221         Extractor ri = recordExtractor(Rint.class, int.class);
 222         Extractor cs = Extractor.ofConstant("foo");
 223         Extractor cn = Extractor.ofConstant(null);
 224         Extractor ci = Extractor.ofConstant(3);
 225 
 226         ExtractorTest.assertMatch(ExtractorTest.MatchKind.MATCH, Extractor.ofNested(rs, cs), new RString("foo"), "foo");
 227         ExtractorTest.assertMatch(ExtractorTest.MatchKind.FAIL, Extractor.ofNested(rs, cs), new RString("bar"));
 228         ExtractorTest.assertMatch(ExtractorTest.MatchKind.FAIL, Extractor.ofNested(rs, cs), new RString(null));
 229 
 230         ExtractorTest.assertMatch(ExtractorTest.MatchKind.MATCH, Extractor.ofNested(ro, cs), new RObject("foo"), "foo");
 231         ExtractorTest.assertMatch(ExtractorTest.MatchKind.FAIL, Extractor.ofNested(ro, cs), new RObject("bar"));
 232         ExtractorTest.assertMatch(ExtractorTest.MatchKind.FAIL, Extractor.ofNested(ro, cs), new RObject(3));
 233         ExtractorTest.assertMatch(ExtractorTest.MatchKind.FAIL, Extractor.ofNested(ro, cs), new RObject(null));
 234 
 235         ExtractorTest.assertMatch(ExtractorTest.MatchKind.MATCH, Extractor.ofNested(ri, ci), new Rint(3), 3);
 236         ExtractorTest.assertMatch(ExtractorTest.MatchKind.FAIL, Extractor.ofNested(ri, ci), new Rint(2));
 237 
 238         ExtractorTest.assertMatch(ExtractorTest.MatchKind.MATCH,
 239                                   Extractor.ofNested(rb, Extractor.ofNested(rs, cs)),
 240                                   new Box(new RString("foo")), new RString("foo"), "foo");
 241         ExtractorTest.assertMatch(ExtractorTest.MatchKind.FAIL,
 242                                   Extractor.ofNested(rb, Extractor.ofNested(rs, cs)),
 243                                   new Box(new RString("bar")));
 244         ExtractorTest.assertMatch(ExtractorTest.MatchKind.FAIL,
 245                                   Extractor.ofNested(rb, Extractor.ofNested(rs, cs)),
 246                                   new Box("foo"));
 247     }
 248 }