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 }