1 /*
  2  * Copyright (c) 2023, 2024, 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  * @test
 26  * @modules java.base/java.lang.runtime:open
 27  *          java.base/jdk.internal.value
 28  *          java.base/jdk.internal.vm.annotation
 29  * @enablePreview
 30  * @run junit/othervm SubstitutabilityTest
 31  */
 32 
 33 import java.lang.reflect.Method;
 34 import java.util.stream.Stream;
 35 
 36 import jdk.internal.value.ValueClass;
 37 import jdk.internal.vm.annotation.ImplicitlyConstructible;
 38 import jdk.internal.vm.annotation.NullRestricted;
 39 
 40 import org.junit.jupiter.api.Test;
 41 import org.junit.jupiter.params.ParameterizedTest;
 42 import org.junit.jupiter.params.provider.Arguments;
 43 import org.junit.jupiter.params.provider.MethodSource;
 44 
 45 import static org.junit.jupiter.api.Assertions.*;
 46 
 47 public class SubstitutabilityTest {
 48     @ImplicitlyConstructible
 49     static value class Point {
 50         public int x;
 51         public int y;
 52         Point(int x, int y) {
 53             this.x = x;
 54             this.y = y;
 55         }
 56     }
 57 
 58     @ImplicitlyConstructible
 59     static value class Line {
 60         @NullRestricted
 61         Point p1;
 62         @NullRestricted
 63         Point p2;
 64 
 65         Line(Point p1, Point p2) {
 66             this.p1 = p1;
 67             this.p2 = p2;
 68         }
 69         Line(int x1, int y1, int x2, int y2) {
 70             this(new Point(x1, y1), new Point(x2, y2));
 71         }
 72     }
 73 
 74     // contains null-reference and null-restricted fields
 75     @ImplicitlyConstructible
 76     static value class MyValue {
 77         MyValue2 v1;
 78         @NullRestricted
 79         MyValue2 v2;
 80         public MyValue(MyValue2 v1, MyValue2 v2) {
 81             this.v1 = v1;
 82             this.v2 = v2;
 83         }
 84     }
 85 
 86     @ImplicitlyConstructible
 87     static value class MyValue2 {
 88         static int cnt = 0;
 89         int x;
 90         MyValue2(int x) {
 91             this.x = x;
 92         }
 93     }
 94 
 95     @ImplicitlyConstructible
 96     static value class MyFloat {
 97         public static float NaN1 = Float.intBitsToFloat(0x7ff00001);
 98         public static float NaN2 = Float.intBitsToFloat(0x7ff00002);
 99         float x;
100         MyFloat(float x) {
101             this.x = x;
102         }
103         public String toString() {
104             return Float.toString(x);
105         }
106     }
107 
108     @ImplicitlyConstructible
109     static value class MyDouble {
110         public static double NaN1 = Double.longBitsToDouble(0x7ff0000000000001L);
111         public static double NaN2 = Double.longBitsToDouble(0x7ff0000000000002L);
112         double x;
113         MyDouble(double x) {
114             this.x = x;
115         }
116         public String toString() {
117             return Double.toString(x);
118         }
119     }
120 
121     static Stream<Arguments> substitutableCases() {
122         Point p1 = new Point(10, 10);
123         Point p2 = new Point(20, 20);
124         Line l1 = new Line(p1, p2);
125         MyValue v1 = new MyValue(null, ValueClass.zeroInstance(MyValue2.class));
126         MyValue v2 = new MyValue(new MyValue2(2), new MyValue2(3));
127         MyValue2 value2 = new MyValue2(2);
128         MyValue2 value3 = new MyValue2(3);
129         MyValue[] va = new MyValue[1];
130         return Stream.of(
131                 Arguments.of(new MyFloat(1.0f), new MyFloat(1.0f)),
132                 Arguments.of(new MyDouble(1.0), new MyDouble(1.0)),
133                 Arguments.of(new MyFloat(Float.NaN), new MyFloat(Float.NaN)),
134                 Arguments.of(new MyDouble(Double.NaN), new MyDouble(Double.NaN)),
135                 Arguments.of(p1, new Point(10, 10)),
136                 Arguments.of(p2, new Point(20, 20)),
137                 Arguments.of(l1, new Line(10,10, 20,20)),
138                 Arguments.of(v1, ValueClass.zeroInstance(MyValue.class)),
139                 Arguments.of(v2, new MyValue(value2, value3)),
140                 Arguments.of(va[0], null)
141         );
142     }
143 
144     @ParameterizedTest
145     @MethodSource("substitutableCases")
146     public void substitutableTest(Object a, Object b) {
147         assertTrue(isSubstitutable(a, b));
148     }
149 
150     static Stream<Arguments> notSubstitutableCases() {
151         // MyValue![] va = new MyValue![1];
152         MyValue[] va = new MyValue[] { ValueClass.zeroInstance(MyValue.class) };
153         Object[] oa = new Object[] { va };
154         return Stream.of(
155                 Arguments.of(new MyFloat(1.0f), new MyFloat(2.0f)),
156                 Arguments.of(new MyDouble(1.0), new MyDouble(2.0)),
157                 Arguments.of(new MyFloat(MyFloat.NaN1), new MyFloat(MyFloat.NaN2)),
158                 Arguments.of(new MyDouble(MyDouble.NaN1), new MyDouble(MyDouble.NaN2)),
159                 Arguments.of(new Point(10, 10), new Point(20, 20)),
160                 /*
161                  * Verify ValueObjectMethods::isSubstitutable that does not
162                  * throw an exception if any one of parameter is null or if
163                  * the parameters are of different types.
164                  */
165                 Arguments.of(va[0], null),
166                 Arguments.of(null, va[0]),
167                 Arguments.of(va[0], oa),
168                 Arguments.of(va[0], oa[0]),
169                 Arguments.of(va, oa),
170                 Arguments.of(new Point(10, 10), Integer.valueOf(10)),
171                 Arguments.of(Integer.valueOf(10), Integer.valueOf(20))
172         );
173     }
174 
175     @ParameterizedTest
176     @MethodSource("notSubstitutableCases")
177     public void notSubstitutableTest(Object a, Object b) {
178         assertFalse(isSubstitutable(a, b));
179     }
180 
181     @Test
182     public void nullArguments() {
183         assertTrue(isSubstitutable(null, null));
184     }
185 
186     private static final Method IS_SUBSTITUTABLE;
187     static {
188         Method m = null;
189         try {
190             Class<?> c = Class.forName("java.lang.runtime.ValueObjectMethods");
191             m = c.getDeclaredMethod("isSubstitutable", Object.class, Object.class);
192             m.setAccessible(true);
193         } catch (ReflectiveOperationException e) {
194             throw new RuntimeException(e);
195         }
196         IS_SUBSTITUTABLE = m;
197     }
198     private static boolean isSubstitutable(Object a, Object b) {
199         try {
200             return (boolean) IS_SUBSTITUTABLE.invoke(null, a, b);
201         } catch (ReflectiveOperationException e) {
202             throw new RuntimeException(e);
203         }
204     }
205 }