1 /*
2 * Copyright (c) 2019, 2026, 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 * @test
26 * @bug 8246774
27 * @summary Basic tests for serializing and deserializing record classes
28 * @run junit RecordClassTest
29 */
30
31 import java.io.ByteArrayInputStream;
32 import java.io.ByteArrayOutputStream;
33 import java.io.Externalizable;
34 import java.io.IOException;
35 import java.io.ObjectInput;
36 import java.io.ObjectInputStream;
37 import java.io.ObjectOutput;
38 import java.io.ObjectOutputStream;
39 import java.io.ObjectStreamClass;
40 import java.io.Serial;
41 import java.io.Serializable;
42 import static java.lang.System.out;
43
44 import static org.junit.jupiter.api.Assertions.assertEquals;
45 import static org.junit.jupiter.api.Assertions.fail;
46 import org.junit.jupiter.api.TestInstance;
47 import org.junit.jupiter.params.ParameterizedTest;
48 import org.junit.jupiter.params.provider.MethodSource;
49
50 /**
51 * Serializes and deserializes record classes. Ensures that the SUID is 0.
52 */
53 public class RecordClassTest {
54
55 record Foo () implements Serializable { }
56
57 record Bar (int x) implements Serializable {
58 @Serial
59 private static final long serialVersionUID = 987654321L;
60 }
61
62 record Baz (Foo foo, Bar bar, int i) implements Serializable { }
63
64 interface ThrowingExternalizable extends Externalizable {
65 default void writeExternal(ObjectOutput out) {
66 fail("should not reach here");
67 }
68 default void readExternal(ObjectInput in) {
69 fail("should not reach here");
70 }
71 }
72
73 record Wibble () implements ThrowingExternalizable {
74 @Serial
75 private static final long serialVersionUID = 12345678L;
76 }
77
78 record Wobble (long l) implements ThrowingExternalizable { }
79
80 record Wubble (Wobble wobble, Wibble wibble, String s) implements ThrowingExternalizable { }
81
82 public static Object[][] recordClasses() {
83 return new Object[][] {
84 new Object[] { Foo.class , 0L },
85 new Object[] { Bar.class , 987654321L },
86 new Object[] { Baz.class , 0L },
87 new Object[] { Wibble.class , 12345678L },
88 new Object[] { Wobble.class , 0L },
89 new Object[] { Wubble.class , 0L },
90 };
91 }
92
93 /** Tests that the serialized and deserialized instances are equal. */
94 @ParameterizedTest
95 @MethodSource("recordClasses")
96 public void testClassSerialization(Class<?> recordClass, long unused)
97 throws Exception
98 {
99 out.println("\n---");
100 out.println("serializing : " + recordClass);
101 var deserializedClass = serializeDeserialize(recordClass);
102 out.println("deserialized: " + deserializedClass);
103 assertEquals(deserializedClass, recordClass);
104 assertEquals(recordClass, deserializedClass);
105 }
106
107 /** Tests that the SUID is always 0 unless explicitly declared. */
108 @ParameterizedTest
109 @MethodSource("recordClasses")
110 public void testSerialVersionUID(Class<?> recordClass, long expectedUID) {
111 out.println("\n---");
112 ObjectStreamClass osc = ObjectStreamClass.lookup(recordClass);
113 out.println("ObjectStreamClass::lookup : " + osc);
114 assertEquals(expectedUID, osc.getSerialVersionUID());
115
116 osc = ObjectStreamClass.lookupAny(recordClass);
117 out.println("ObjectStreamClass::lookupAny: " + osc);
118 assertEquals(expectedUID, osc.getSerialVersionUID());
119 }
120
121 // --- not Serializable
122
123 record NotSerializable1() { }
124
125 record NotSerializable2(int x) { }
126
127 record NotSerializable3<T>(T t) { }
128
129 public static Object[][] notSerRecordClasses() {
130 return new Object[][] {
131 new Object[] { NotSerializable1.class },
132 new Object[] { NotSerializable2.class },
133 new Object[] { NotSerializable3.class },
134 };
135 }
136
137 /** Tests that the generated SUID is always 0 for all non-Serializable record classes. */
138 @ParameterizedTest
139 @MethodSource("notSerRecordClasses")
140 public void testSerialVersionUIDNonSer(Class<?> recordClass) {
141 out.println("\n---");
142 ObjectStreamClass osc = ObjectStreamClass.lookup(recordClass);
143 out.println("ObjectStreamClass::lookup : " + osc);
144 assertEquals(null, osc);
145
146 osc = ObjectStreamClass.lookupAny(recordClass);
147 out.println("ObjectStreamClass::lookupAny: " + osc);
148 assertEquals(0L, osc.getSerialVersionUID());
149 }
150
151 // --- infra
152
153 static <T> byte[] serialize(T obj) throws IOException {
154 ByteArrayOutputStream baos = new ByteArrayOutputStream();
155 ObjectOutputStream oos = new ObjectOutputStream(baos);
156 oos.writeObject(obj);
157 oos.close();
158 return baos.toByteArray();
159 }
160
161 @SuppressWarnings("unchecked")
162 static <T> T deserialize(byte[] streamBytes)
163 throws IOException, ClassNotFoundException
164 {
165 ByteArrayInputStream bais = new ByteArrayInputStream(streamBytes);
166 ObjectInputStream ois = new ObjectInputStream(bais);
167 return (T) ois.readObject();
168 }
169
170 static <T> T serializeDeserialize(T obj)
171 throws IOException, ClassNotFoundException
172 {
173 return deserialize(serialize(obj));
174 }
175 }