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