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