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 }