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