1 /* 2 * Copyright (c) 2018, 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 /* 26 * @test 27 * @summary test Object methods on value classes 28 * @enablePreview 29 * @run junit/othervm -Dvalue.bsm.salt=1 ObjectMethods 30 * @run junit/othervm -Dvalue.bsm.salt=1 -XX:-UseFieldFlattening ObjectMethods 31 */ 32 import java.util.List; 33 import java.util.Objects; 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 import org.junit.jupiter.api.Test; 40 import org.junit.jupiter.params.ParameterizedTest; 41 import org.junit.jupiter.params.provider.Arguments; 42 import org.junit.jupiter.params.provider.MethodSource; 43 44 import static org.junit.jupiter.api.Assertions.*; 45 46 public class ObjectMethods { 47 static value class Point { 48 public int x; 49 public int y; 50 Point(int x, int y) { 51 this.x = x; 52 this.y = y; 53 } 54 } 55 56 static value class Line { 57 @NullRestricted @Strict 58 Point p1; 59 @NullRestricted @Strict 60 Point p2; 61 62 Line(int x1, int y1, int x2, int y2) { 63 this.p1 = new Point(x1, y1); 64 this.p2 = new Point(x2, y2); 65 } 66 } 67 68 static class Ref { 69 @NullRestricted @Strict 70 Point p; 71 Line l; 72 Ref(Point p, Line l) { 73 this.p = p; 74 this.l = l; 75 } 76 } 77 78 static value class Value { 79 @NullRestricted @Strict 80 Point p; 81 @NullRestricted @Strict 82 Line l; 83 Ref r; 84 String s; 85 Value(Point p, Line l, Ref r, String s) { 86 this.p = p; 87 this.l = l; 88 this.r = r; 89 this.s = s; 90 } 91 } 92 93 static value class ValueOptional { 94 private Object o; 95 public ValueOptional(Object o) { 96 this.o = o; 97 } 98 } 99 100 static value record ValueRecord(int i, String name) {} 101 102 static final int SALT = 1; 103 static final Point P1 = new Point(1, 2); 104 static final Point P2 = new Point(30, 40); 105 static final Line L1 = new Line(1, 2, 3, 4); 106 static final Line L2 = new Line(10, 20, 3, 4); 107 static final Ref R1 = new Ref(P1, L1); 108 static final Ref R2 = new Ref(P2, null); 109 static final Value V = new Value(P1, L1, R1, "value"); 110 111 static Stream<Arguments> identitiesData() { 112 return Stream.of( 113 Arguments.of(new Object(), true, false), 114 Arguments.of("String", true, false), 115 Arguments.of(String.class, true, false), 116 Arguments.of(Object.class, true, false), 117 Arguments.of(L1, false, true), 118 Arguments.of(V, false, true), 119 Arguments.of(new ValueRecord(1, "B"), false, true), 120 Arguments.of(new int[0], true, false), // arrays of primitive type are identity objects 121 Arguments.of(new Object[0], true, false), // arrays of identity classes are identity objects 122 Arguments.of(new String[0], true, false), // arrays of identity classes are identity objects 123 Arguments.of(new Value[0], true, false) // arrays of value classes are identity objects 124 ); 125 } 126 127 @ParameterizedTest 128 @MethodSource("identitiesData") 129 public void identityTests(Object obj, boolean identityClass, boolean valueClass) { 130 Class<?> clazz = obj.getClass(); 131 132 if (clazz == Object.class) { 133 assertTrue(Objects.hasIdentity(obj), "Objects.hasIdentity()"); 134 } else { 135 assertEquals(identityClass, Objects.hasIdentity(obj), "Objects.hasIdentity()"); 136 } 137 138 assertEquals(identityClass, clazz.isIdentity(), "Class.isIdentity()"); 139 140 assertEquals(valueClass, clazz.isValue(), "Class.isValue()"); 141 142 // JDK-8294866: Not yet implemented checks of AccessFlags for the array class 143 // assertEquals(clazz.accessFlags().contains(AccessFlag.IDENTITY), 144 // identityClass, "AccessFlag.IDENTITY"); 145 // 146 // assertEquals(clazz.accessFlags().contains(AccessFlag.VALUE), 147 // valueClass, "AccessFlag.VALUE"); 148 } 149 150 static Stream<Arguments> equalsTests() { 151 return Stream.of( 152 Arguments.of(P1, P1, true), 153 Arguments.of(P1, new Point(1, 2), true), 154 Arguments.of(P1, P2, false), 155 Arguments.of(P1, L1, false), 156 Arguments.of(L1, new Line(1, 2, 3, 4), true), 157 Arguments.of(L1, L2, false), 158 Arguments.of(L1, L1, true), 159 Arguments.of(V, new Value(P1, L1, R1, "value"), true), 160 Arguments.of(V, new Value(new Point(1, 2), new Line(1, 2, 3, 4), R1, "value"), true), 161 Arguments.of(V, new Value(P1, L1, new Ref(P1, L1), "value"), false), 162 Arguments.of(new Value(P1, L1, R2, "value2"), new Value(P1, L1, new Ref(P2, null), "value2"), false), 163 Arguments.of(new ValueRecord(50, "fifty"), new ValueRecord(50, "fifty"), true), 164 165 // reference classes containing fields of value class 166 Arguments.of(R1, new Ref(P1, L1), false), // identity object 167 168 // uninitialized default value 169 Arguments.of(new ValueOptional(L1), new ValueOptional(L1), true), 170 Arguments.of(new ValueOptional(List.of(P1)), new ValueOptional(List.of(P1)), false) 171 ); 172 } 173 174 @ParameterizedTest 175 @MethodSource("equalsTests") 176 public void testEquals(Object o1, Object o2, boolean expected) { 177 assertTrue(o1.equals(o2) == expected); 178 } 179 180 static Stream<Arguments> toStringTests() { 181 return Stream.of( 182 Arguments.of(new Point(100, 200)), 183 Arguments.of(new Line(1, 2, 3, 4)), 184 Arguments.of(V), 185 Arguments.of(R1), 186 // enclosing instance field `this$0` should be filtered 187 Arguments.of(new Value(P1, L1, null, null)), 188 Arguments.of(new Value(P2, L2, new Ref(P1, null), "value")), 189 Arguments.of(new ValueOptional(P1)) 190 ); 191 } 192 193 @ParameterizedTest 194 @MethodSource("toStringTests") 195 public void testToString(Object o) { 196 String expected = String.format("%s@%s", o.getClass().getName(), Integer.toHexString(o.hashCode())); 197 assertEquals(o.toString(), expected); 198 } 199 200 @Test 201 public void testValueRecordToString() { 202 ValueRecord o = new ValueRecord(30, "thirty"); 203 assertEquals(o.toString(), "ValueRecord[i=30, name=thirty]"); 204 } 205 206 static Stream<Arguments> hashcodeTests() { 207 Point p = new Point(0, 0); 208 Line l = new Line(0, 0, 0, 0); 209 // this is sensitive to the order of the returned fields from Class::getDeclaredFields 210 return Stream.of( 211 Arguments.of(P1, hash(Point.class, 1, 2)), 212 Arguments.of(L1, hash(Line.class, new Point(1, 2), new Point(3, 4))), 213 Arguments.of(V, hash(Value.class, P1, L1, V.r, V.s)), 214 Arguments.of(new Point(0, 0), hash(Point.class, 0, 0)), 215 Arguments.of(p, hash(Point.class, 0, 0)), 216 Arguments.of(new ValueOptional(P1), hash(ValueOptional.class, P1)) 217 ); 218 } 219 220 @ParameterizedTest 221 @MethodSource("hashcodeTests") 222 public void testHashCode(Object o, int hash) { 223 assertEquals(o.hashCode(), hash); 224 assertEquals(System.identityHashCode(o), hash); 225 } 226 227 private static int hash(Object... values) { 228 int hc = SALT; 229 for (Object o : values) { 230 hc = 31 * hc + (o != null ? o.hashCode() : 0); 231 } 232 return hc; 233 } 234 235 interface Number { 236 int value(); 237 } 238 239 static class ReferenceType implements Number { 240 int i; 241 public ReferenceType(int i) { 242 this.i = i; 243 } 244 public int value() { 245 return i; 246 } 247 @Override 248 public boolean equals(Object o) { 249 if (o != null && o instanceof Number) { 250 return this.value() == ((Number)o).value(); 251 } 252 return false; 253 } 254 } 255 256 static value class ValueType1 implements Number { 257 int i; 258 public ValueType1(int i) { 259 this.i = i; 260 } 261 public int value() { 262 return i; 263 } 264 } 265 266 static value class ValueType2 implements Number { 267 int i; 268 public ValueType2(int i) { 269 this.i = i; 270 } 271 public int value() { 272 return i; 273 } 274 @Override 275 public boolean equals(Object o) { 276 if (o != null && o instanceof Number) { 277 return this.value() == ((Number)o).value(); 278 } 279 return false; 280 } 281 } 282 283 static Stream<Arguments> interfaceEqualsTests() { 284 return Stream.of( 285 Arguments.of(new ReferenceType(10), new ReferenceType(10), false, true), 286 Arguments.of(new ValueType1(10), new ValueType1(10), true, true), 287 Arguments.of(new ValueType2(10), new ValueType2(10), true, true), 288 Arguments.of(new ValueType1(20), new ValueType2(20), false, false), 289 Arguments.of(new ValueType2(20), new ValueType1(20), false, true), 290 Arguments.of(new ReferenceType(30), new ValueType1(30), false, true), 291 Arguments.of(new ReferenceType(30), new ValueType2(30), false, true) 292 ); 293 } 294 295 @ParameterizedTest 296 @MethodSource("interfaceEqualsTests") 297 public void testNumber(Number n1, Number n2, boolean isSubstitutable, boolean isEquals) { 298 assertTrue((n1 == n2) == isSubstitutable); 299 assertTrue(n1.equals(n2) == isEquals); 300 } 301 }