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 }