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