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 }