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 }