1 /*
2 * Copyright (c) 2019, 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 * @test
26 * @bug 8246774
27 * @summary Basic test that serializes and deserializes a number of records
28 * @run junit BasicRecordSer
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.NotSerializableException;
36 import java.io.ObjectInput;
37 import java.io.ObjectInputStream;
38 import java.io.ObjectOutput;
39 import java.io.ObjectOutputStream;
40 import java.io.Serializable;
41 import java.math.BigInteger;
42 import static java.lang.String.format;
43 import static java.lang.System.out;
44 import static java.net.InetAddress.getLoopbackAddress;
45
46 import org.junit.jupiter.api.Assertions;
47 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
48 import static org.junit.jupiter.api.Assertions.assertEquals;
49 import static org.junit.jupiter.api.Assertions.assertTrue;
50 import static org.junit.jupiter.api.Assertions.fail;
51 import org.junit.jupiter.api.Test;
52 import org.junit.jupiter.api.TestInstance;
53 import org.junit.jupiter.params.ParameterizedTest;
54 import org.junit.jupiter.params.provider.MethodSource;
55
56 /**
57 * Basic test that serializes and deserializes a number of simple records.
58 */
59 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
60 public class BasicRecordSer {
61
62 // a mix of a few record and non-record classes
63
64 record Empty () implements Serializable { }
65
66 record Foo (int i) implements Serializable { }
67
68 static class Bar implements Serializable {
69 final Foo foo;
70 final long along;
71 Bar(Foo foo, long along) { this.foo = foo; this.along = along; }
72 @Override
73 public boolean equals(Object obj) {
74 if (!(obj instanceof Bar))
75 return false;
76 Bar other = (Bar)obj;
77 if (this.foo.equals(other.foo) && this.along == other.along)
78 return true;
79 return false;
80 }
81 @Override
82 public String toString() {
83 return format("Bar[foo=%s, along=%d]", foo, along);
84 }
85 }
86
87 record Baz (Bar bar, float afloat, Foo foo) implements Serializable { }
88
89 record Bat (Empty e1, Foo foo1, Bar bar1, float afloat, Foo foo2, Empty e2, Bar bar2)
90 implements Serializable { }
91
92 record Cheese<A, B>(A a, B b) implements Serializable { }
93
94 interface ThrowingExternalizable extends Externalizable {
95 default void writeExternal(ObjectOutput out) {
96 fail("should not reach here");
97 }
98 default void readExternal(ObjectInput in) {
99 fail("should not reach here");
100 }
101 }
102
103 record Wibble () implements ThrowingExternalizable { }
104
105 record Wobble (Foo foo) implements ThrowingExternalizable { }
106
107 record Wubble (Wobble wobble, Wibble wibble, String s) implements ThrowingExternalizable { }
108
109 public Object[][] serializable() {
110 Foo foo = new Foo(23);
111 return new Object[][] {
112 new Object[] { new Empty() },
113 new Object[] { new Foo(22) },
114 new Object[] { new Foo[] { new Foo(24), new Foo(25) } },
115 new Object[] { new Foo[] { foo, foo, foo, foo, foo } },
116 new Object[] { new Bar(new Foo(33), 1_234_567L) },
117 new Object[] { new Baz(new Bar(new Foo(44), 4_444L), 5.5f, new Foo(55)) },
118 new Object[] { new Bat(new Empty(), new Foo(57), new Bar(new Foo(44), 4_444L),
119 5.5f, new Foo(55), new Empty(), new Bar(new Foo(23), 1L)) },
120 new Object[] { new Cheese(getLoopbackAddress(), BigInteger.valueOf(78)) },
121 new Object[] { new Wibble() },
122 new Object[] { new Wobble(new Foo(65)) },
123 new Object[] { new Wubble(new Wobble(new Foo(6)), new Wibble(), "xxzzzyy") },
124 };
125 }
126
127 /** Tests serializing and deserializing a number of records. */
128 @ParameterizedTest
129 @MethodSource("serializable")
130 public void testSerializable(Object objToSerialize) throws Exception {
131 out.println("\n---");
132 out.println("serializing : " + objToSerialize);
133 var objDeserialized = serializeDeserialize(objToSerialize);
134 out.println("deserialized: " + objDeserialized);
135 if (objToSerialize.getClass().isArray()) {
136 assertArrayEquals((Object[]) objDeserialized, (Object[]) objToSerialize);
137 assertArrayEquals((Object[]) objToSerialize, (Object[]) objDeserialized);
138 } else {
139 assertEquals(objDeserialized, objToSerialize);
140 assertEquals(objToSerialize, objDeserialized);
141 }
142 }
143
144 /** Tests serializing and deserializing of local records. */
145 @Test
146 public void testLocalRecord() throws Exception {
147 out.println("\n---");
148 record Point(int x, int y) implements Serializable { }
149 record Rectangle(Point bottomLeft, Point topRight) implements Serializable { }
150 var objToSerialize = new Rectangle(new Point(0, 1), new Point (5, 6));
151 out.println("serializing : " + objToSerialize);
152 var objDeserialized = serializeDeserialize(objToSerialize);
153 out.println("deserialized: " + objDeserialized);
154 assertEquals(objToSerialize, objDeserialized);
155 assertEquals(objDeserialized, objToSerialize);
156 }
157
158 /** Tests back references of Serializable record objects in the stream. */
159 @Test
160 public void testSerializableBackRefs() throws Exception {
161 out.println("\n---");
162 Foo foo = new Foo(32);
163 Foo[] objToSerialize = new Foo[] { foo, foo, foo, foo, foo };
164 out.println("serializing : " + objToSerialize);
165 Foo[] objDeserialized = (Foo[])serializeDeserialize(objToSerialize);
166 out.println("deserialized: " + objDeserialized);
167 Assertions.assertArrayEquals(objDeserialized, objToSerialize);
168 Assertions.assertArrayEquals(objToSerialize, objDeserialized);
169
170 for (Foo f : objDeserialized)
171 assertTrue(objDeserialized[0] == f);
172 }
173
174 /** Tests back references of Externalizable record objects in the stream. */
175 @Test
176 public void testExternalizableBackRefs() throws Exception {
177 out.println("\n---");
178 Foo foo = new Foo(33);
179 Wobble wobble = new Wobble(foo);
180 Wobble[] objToSerialize = new Wobble[] { wobble, wobble, wobble, wobble };
181 out.println("serializing : " + objToSerialize);
182 Wobble[] objDeserialized = (Wobble[])serializeDeserialize(objToSerialize);
183 out.println("deserialized: " + objDeserialized);
184 Assertions.assertArrayEquals(objDeserialized, objToSerialize);
185 Assertions.assertArrayEquals(objToSerialize, objDeserialized);
186
187 for (Wobble w : objDeserialized) {
188 assertTrue(objDeserialized[0] == w);
189 assertTrue(objDeserialized[0].foo() == w.foo());
190 }
191 }
192
193 // --- Not Serializable
194
195 record NotSerEmpty () { }
196 record NotSer (int x) { }
197 record NotSerA (int x, int y) {
198 private static final long serialVersionUID = 5L;
199 }
200 static class A implements Serializable {
201 final int y = -1;
202 final NotSer notSer = new NotSer(7);
203 }
204
205 public Object[][] notSerializable() {
206 return new Object[][] {
207 new Object[] { new NotSerEmpty() },
208 new Object[] { new NotSerEmpty[] { new NotSerEmpty() } },
209 new Object[] { new Object[] { new NotSerEmpty() } },
210 new Object[] { new NotSer(6) },
211 new Object[] { new NotSer[] { new NotSer(7) } },
212 new Object[] { new NotSerA(6, 8) },
213 new Object[] { new A() },
214 new Object[] { new A[] { new A() } },
215 };
216 }
217
218 static final Class<NotSerializableException> NSE = NotSerializableException.class;
219
220 /** Tests that non-Serializable record objects throw NotSerializableException. */
221 @ParameterizedTest
222 @MethodSource("notSerializable")
223 public void testNotSerializable(Object objToSerialize) throws Exception {
224 out.println("\n---");
225 out.println("serializing : " + objToSerialize);
226 NotSerializableException expected = Assertions.assertThrows(NSE, () -> serialize(objToSerialize));
227 out.println("caught expected NSE:" + expected);
228
229 }
230
231 // --- constructor invocation counting
232
233 static volatile int e_ctrInvocationCount;
234
235 record E () implements Serializable {
236 public E() { e_ctrInvocationCount++; }
237 }
238
239 /** Tests that the record's constructor is invoke exactly once per deserialization. */
240 @Test
241 public void testCtrCalledOnlyOnce() throws Exception {
242 out.println("\n---");
243 var objToSerialize = new E();
244 e_ctrInvocationCount = 0; // reset
245 out.println("serializing : " + objToSerialize);
246 var objDeserialized = serializeDeserialize(objToSerialize);
247 out.println("deserialized: " + objDeserialized);
248 assertEquals(objDeserialized, objToSerialize);
249 assertEquals(objToSerialize, objDeserialized);
250 assertEquals(1, e_ctrInvocationCount);
251 }
252
253 // ---
254
255 static volatile int g_ctrInvocationCount;
256
257 record F (int x){
258 public F(int x) { this.x = x; g_ctrInvocationCount++; }
259 }
260 static class G implements Serializable {
261 F f = new F(89);
262 }
263
264 /** Tests that the record's constructor is NOT invoke during failed deserialization. */
265 @Test
266 public void testCtrNotCalled() {
267 out.println("\n---");
268 var objToSerialize = new G();
269 g_ctrInvocationCount = 0; // reset
270 out.println("serializing : " + objToSerialize);
271 NotSerializableException expected = Assertions.assertThrows(NSE, () -> serialize(objToSerialize));
272 out.println("caught expected NSE:" + expected);
273 assertEquals(0, g_ctrInvocationCount);
274 }
275
276 // --- infra
277
278 static <T> byte[] serialize(T obj) throws IOException {
279 ByteArrayOutputStream baos = new ByteArrayOutputStream();
280 ObjectOutputStream oos = new ObjectOutputStream(baos);
281 oos.writeObject(obj);
282 oos.close();
283 return baos.toByteArray();
284 }
285
286 @SuppressWarnings("unchecked")
287 static <T> T deserialize(byte[] streamBytes)
288 throws IOException, ClassNotFoundException
289 {
290 ByteArrayInputStream bais = new ByteArrayInputStream(streamBytes);
291 ObjectInputStream ois = new ObjectInputStream(bais);
292 return (T) ois.readObject();
293 }
294
295 static <T> T serializeDeserialize(T obj)
296 throws IOException, ClassNotFoundException
297 {
298 return deserialize(serialize(obj));
299 }
300 }