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