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.hasIdentity(obj), "Objects.hasIdentity()"); 138 } else { 139 assertEquals(identityClass, Objects.hasIdentity(obj), "Objects.hasIdentity()"); 140 } 141 142 assertEquals(identityClass, clazz.isIdentity(), "Class.isIdentity()"); 143 144 assertEquals(valueClass, clazz.isValue(), "Class.isValue()"); 145 146 // JDK-8294866: Not yet implemented checks of AccessFlags for the array class 147 // assertEquals(clazz.accessFlags().contains(AccessFlag.IDENTITY), 148 // identityClass, "AccessFlag.IDENTITY"); 149 // 150 // assertEquals(clazz.accessFlags().contains(AccessFlag.VALUE), 151 // valueClass, "AccessFlag.VALUE"); 152 } 153 154 static Stream<Arguments> equalsTests() { 155 return Stream.of( 156 Arguments.of(P1, P1, true), 157 Arguments.of(P1, new Point(1, 2), true), 158 Arguments.of(P1, P2, false), 159 Arguments.of(P1, L1, false), 160 Arguments.of(L1, new Line(1, 2, 3, 4), true), 161 Arguments.of(L1, L2, false), 162 Arguments.of(L1, L1, true), 163 Arguments.of(V, new Value(P1, L1, R1, "value"), true), 164 Arguments.of(V, new Value(new Point(1, 2), new Line(1, 2, 3, 4), R1, "value"), true), 165 Arguments.of(V, new Value(P1, L1, new Ref(P1, L1), "value"), false), 166 Arguments.of(new Value(P1, L1, R2, "value2"), new Value(P1, L1, new Ref(P2, null), "value2"), false), 167 Arguments.of(new ValueRecord(50, "fifty"), new ValueRecord(50, "fifty"), true), 168 169 // reference classes containing fields of value class 170 Arguments.of(R1, new Ref(P1, L1), false), // identity object 171 172 // uninitialized default value 173 Arguments.of(ValueClass.zeroInstance(Line.class), new Line(0, 0, 0, 0), true), 174 Arguments.of(ValueClass.zeroInstance(Value.class), ValueClass.zeroInstance(Value.class), true), 175 Arguments.of(new ValueOptional(L1), new ValueOptional(L1), true), 176 Arguments.of(new ValueOptional(List.of(P1)), new ValueOptional(List.of(P1)), false) 177 ); 178 } 179 180 @ParameterizedTest 181 @MethodSource("equalsTests") 182 public void testEquals(Object o1, Object o2, boolean expected) { 183 assertTrue(o1.equals(o2) == expected); 184 } 185 186 static Stream<Arguments> toStringTests() { 187 return Stream.of( 188 Arguments.of(new Point(100, 200)), 189 Arguments.of(new Line(1, 2, 3, 4)), 190 Arguments.of(V), 191 Arguments.of(R1), 192 // enclosing instance field `this$0` should be filtered 193 Arguments.of(ValueClass.zeroInstance(Value.class)), 194 Arguments.of(new Value(P1, L1, null, null)), 195 Arguments.of(new Value(P2, L2, new Ref(P1, null), "value")), 196 Arguments.of(ValueClass.zeroInstance(ValueOptional.class)), 197 Arguments.of(new ValueOptional(P1)) 198 ); 199 } 200 201 @ParameterizedTest 202 @MethodSource("toStringTests") 203 public void testToString(Object o) { 204 String expected = String.format("%s@%s", o.getClass().getName(), Integer.toHexString(o.hashCode())); 205 assertEquals(o.toString(), expected); 206 } 207 208 @Test 209 public void testValueRecordToString() { 210 ValueRecord o = new ValueRecord(30, "thirty"); 211 assertEquals(o.toString(), "ValueRecord[i=30, name=thirty]"); 212 } 213 214 215 static Stream<Arguments> hashcodeTests() { 216 Point p = ValueClass.zeroInstance(Point.class); 217 Line l = ValueClass.zeroInstance(Line.class); 218 Value v = ValueClass.zeroInstance(Value.class); 219 // this is sensitive to the order of the returned fields from Class::getDeclaredFields 220 return Stream.of( 221 Arguments.of(P1, hash(Point.class, 1, 2)), 222 Arguments.of(L1, hash(Line.class, new Point(1, 2), new Point(3, 4))), 223 Arguments.of(V, hash(Value.class, P1, L1, V.r, V.s)), 224 Arguments.of(new Point(0, 0), hash(Point.class, 0, 0)), 225 Arguments.of(p, hash(Point.class, 0, 0)), 226 Arguments.of(v, hash(Value.class, p, l, null, null)), 227 Arguments.of(new ValueOptional(P1), hash(ValueOptional.class, P1)) 228 ); 229 } 230 231 @ParameterizedTest 232 @MethodSource("hashcodeTests") 233 public void testHashCode(Object o, int hash) { 234 assertEquals(o.hashCode(), hash); 235 assertEquals(System.identityHashCode(o), hash); 236 } 237 238 private static int hash(Object... values) { 239 int hc = SALT; 240 for (Object o : values) { 241 hc = 31 * hc + (o != null ? o.hashCode() : 0); 242 } 243 return hc; 244 } 245 246 interface Number { 247 int value(); 248 } 249 250 static class ReferenceType implements Number { 251 int i; 252 public ReferenceType(int i) { 253 this.i = i; 254 } 255 public int value() { 256 return i; 257 } 258 @Override 259 public boolean equals(Object o) { 260 if (o != null && o instanceof Number) { 261 return this.value() == ((Number)o).value(); 262 } 263 return false; 264 } 265 } 266 267 @ImplicitlyConstructible 268 static value class ValueType1 implements Number { 269 int i; 270 public ValueType1(int i) { 271 this.i = i; 272 } 273 public int value() { 274 return i; 275 } 276 } 277 278 @ImplicitlyConstructible 279 static value class ValueType2 implements Number { 280 int i; 281 public ValueType2(int i) { 282 this.i = i; 283 } 284 public int value() { 285 return i; 286 } 287 @Override 288 public boolean equals(Object o) { 289 if (o != null && o instanceof Number) { 290 return this.value() == ((Number)o).value(); 291 } 292 return false; 293 } 294 } 295 296 static Stream<Arguments> interfaceEqualsTests() { 297 return Stream.of( 298 Arguments.of(new ReferenceType(10), new ReferenceType(10), false, true), 299 Arguments.of(new ValueType1(10), new ValueType1(10), true, true), 300 Arguments.of(new ValueType2(10), new ValueType2(10), true, true), 301 Arguments.of(new ValueType1(20), new ValueType2(20), false, false), 302 Arguments.of(new ValueType2(20), new ValueType1(20), false, true), 303 Arguments.of(new ReferenceType(30), new ValueType1(30), false, true), 304 Arguments.of(new ReferenceType(30), new ValueType2(30), false, true) 305 ); 306 } 307 308 @ParameterizedTest 309 @MethodSource("interfaceEqualsTests") 310 public void testNumber(Number n1, Number n2, boolean isSubstitutable, boolean isEquals) { 311 assertTrue((n1 == n2) == isSubstitutable); 312 assertTrue(n1.equals(n2) == isEquals); 313 } 314 }