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 * @summary ValueSerialization support of value classes 27 * @enablePreview 28 * @compile ValueSerialization.java 29 * @run testng/othervm ValueSerialization 30 */ 31 32 import java.io.ByteArrayInputStream; 33 import java.io.ByteArrayOutputStream; 34 import java.io.DataOutputStream; 35 import java.io.Externalizable; 36 import java.io.IOException; 37 import java.io.InvalidClassException; 38 import java.io.InvalidObjectException; 39 import java.io.NotSerializableException; 40 import java.io.ObjectInput; 41 import java.io.ObjectInputStream; 42 import java.io.ObjectOutput; 43 import java.io.ObjectOutputStream; 44 import java.io.ObjectStreamClass; 45 import java.io.ObjectStreamException; 46 import java.io.Serializable; 47 import org.testng.annotations.DataProvider; 48 import org.testng.annotations.Test; 49 import static java.io.ObjectStreamConstants.*; 50 import static org.testng.Assert.assertEquals; 51 import static org.testng.Assert.assertThrows; 52 import static org.testng.Assert.expectThrows; 53 54 public class ValueSerialization { 55 56 static final Class<NotSerializableException> NSE = NotSerializableException.class; 57 58 @DataProvider(name = "doesNotImplementSerializable") 59 public Object[][] doesNotImplementSerializable() { 60 return new Object[][] { 61 new Object[] { new NonSerializablePoint(10, 100) }, 62 // an array of Points 63 new Object[] { new NonSerializablePoint[] {new NonSerializablePoint(1, 5)} }, 64 new Object[] { new Object[] {new NonSerializablePoint(3, 7)} }, 65 new Object[] { new ExternalizablePoint(12, 102) }, 66 new Object[] { new ExternalizablePoint[] { 67 new ExternalizablePoint(3, 7), 68 new ExternalizablePoint(2, 8) } }, 69 new Object[] { new Object[] { 70 new ExternalizablePoint(13, 17), 71 new ExternalizablePoint(14, 18) } }, 72 }; 73 } 74 75 // value class that DOES NOT implement Serializable should throw NSE 76 @Test(dataProvider = "doesNotImplementSerializable") 77 public void doesNotImplementSerializable(Object obj) { 78 assertThrows(NSE, () -> serialize(obj)); 79 } 80 81 /** Non-Serializable point. */ 82 public static value class NonSerializablePoint { 83 public int x; 84 public int y; 85 86 public NonSerializablePoint(int x, int y) { 87 this.x = x; 88 this.y = y; 89 } 90 } 91 92 /** A Serializable value class Point */ 93 static value class SerializablePoint implements Serializable { 94 public int x; 95 public int y; 96 SerializablePoint(int x, int y) { this.x = x; this.y = y; } 97 @Override public String toString() { 98 return "[SerializablePoint x=" + x + " y=" + y + "]"; } 99 } 100 101 /** A Serializable value class Point */ 102 static value class SerializablePrimitivePoint implements Serializable { 103 public int x; 104 public int y; 105 SerializablePrimitivePoint(int x, int y) { this.x = x; this.y = y; } 106 @Override public String toString() { 107 return "[SerializablePrimitivePoint x=" + x + " y=" + y + "]"; } 108 } 109 110 /** An Externalizable Point is not Serializable, readExternal cannot modify fields */ 111 static value class ExternalizablePoint implements Externalizable { 112 public int x; 113 public int y; 114 ExternalizablePoint(int x, int y) { this.x = x; this.y = y; } 115 @Override public void readExternal(ObjectInput in) { } 116 @Override public void writeExternal(ObjectOutput out) { } 117 @Override public String toString() { 118 return "[ExternalizablePoint x=" + x + " y=" + y + "]"; } 119 } 120 121 @DataProvider(name = "doesImplementSerializable") 122 public Object[][] doesImplementSerializable() { 123 return new Object[][] { 124 new Object[] { new SerializablePoint(11, 101) }, 125 new Object[] { new SerializablePoint[] { 126 new SerializablePoint(1, 5), 127 new SerializablePoint(2, 6) } }, 128 new Object[] { new Object[] { 129 new SerializablePoint(3, 7), 130 new SerializablePoint(4, 8) } }, 131 new Object[] { new SerializablePrimitivePoint(711, 7101) }, 132 new Object[] { new SerializablePrimitivePoint[] { 133 new SerializablePrimitivePoint(71, 75), 134 new SerializablePrimitivePoint(72, 76) } }, 135 new Object[] { new Object[] { 136 new SerializablePrimitivePoint(73, 77), 137 new SerializablePrimitivePoint(74, 78) } }, 138 }; 139 } 140 141 // value class that DOES implement Serializable all supported 142 @Test(dataProvider = "doesImplementSerializable") 143 public void doesImplementSerializable(Object obj) throws IOException { 144 serialize(obj); 145 } 146 147 /** A Serializable Foo, with a serial proxy */ 148 static value class SerializableFoo implements Serializable { 149 public int x; 150 SerializableFoo(int x) { this.x = x; } 151 152 Object writeReplace() throws ObjectStreamException { 153 return new SerialFooProxy(x); 154 } 155 private void readObject(ObjectInputStream s) throws InvalidObjectException { 156 throw new InvalidObjectException("Proxy required"); 157 } 158 private static class SerialFooProxy implements Serializable { 159 final int x; 160 SerialFooProxy(int x) { this.x = x; } 161 Object readResolve() throws ObjectStreamException { 162 return new SerializableFoo(this.x); 163 } 164 } 165 } 166 167 /** An Externalizable Foo, with a serial proxy */ 168 static value class ExternalizableFoo implements Externalizable { 169 public String s; 170 ExternalizableFoo(String s) { this.s = s; } 171 Object writeReplace() throws ObjectStreamException { 172 return new SerialFooProxy(s); 173 } 174 private void readObject(ObjectInputStream s) throws InvalidObjectException { 175 throw new InvalidObjectException("Proxy required"); 176 } 177 private static class SerialFooProxy implements Serializable { 178 final String s; 179 SerialFooProxy(String s) { this.s = s; } 180 Object readResolve() throws ObjectStreamException { 181 return new ExternalizableFoo(this.s); 182 } 183 } 184 @Override public void readExternal(ObjectInput in) { } 185 @Override public void writeExternal(ObjectOutput out) { } 186 } 187 188 // value classes that DO implement Serializable, but have a serial proxy 189 @Test 190 public void serializableFooWithProxy() throws Exception { 191 SerializableFoo foo = new SerializableFoo(45); 192 SerializableFoo foo1 = serializeDeserialize(foo); 193 assertEquals(foo.x, foo1.x); 194 } 195 @Test 196 public void serializableFooArrayWithProxy() throws Exception { 197 SerializableFoo[] fooArray = new SerializableFoo[]{new SerializableFoo(46)}; 198 SerializableFoo[] fooArray1 = serializeDeserialize(fooArray); 199 assertEquals(fooArray.length, fooArray1.length); 200 assertEquals(fooArray[0].x, fooArray1[0].x); 201 } 202 @Test 203 public void externalizableFooWithProxy() throws Exception { 204 ExternalizableFoo foo = new ExternalizableFoo("hello"); 205 ExternalizableFoo foo1 = serializeDeserialize(foo); 206 assertEquals(foo.s, foo1.s); 207 } 208 @Test 209 public void externalizableFooArrayWithProxy() throws Exception { 210 ExternalizableFoo[] fooArray = new ExternalizableFoo[] { new ExternalizableFoo("there") }; 211 ExternalizableFoo[] fooArray1 = serializeDeserialize(fooArray); 212 assertEquals(fooArray.length, fooArray1.length); 213 assertEquals(fooArray[0].s, fooArray1[0].s); 214 } 215 216 private static byte[] byteStreamFor(String className, long uid, byte flags) 217 throws Exception 218 { 219 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 220 DataOutputStream dos = new DataOutputStream(baos); 221 dos.writeShort(STREAM_MAGIC); 222 dos.writeShort(STREAM_VERSION); 223 dos.writeByte(TC_OBJECT); 224 dos.writeByte(TC_CLASSDESC); 225 dos.writeUTF(className); 226 dos.writeLong(uid); 227 dos.writeByte(flags); 228 dos.writeShort(0); // number of fields 229 dos.writeByte(TC_ENDBLOCKDATA); // no annotations 230 dos.writeByte(TC_NULL); // no superclasses 231 dos.close(); 232 return baos.toByteArray(); 233 } 234 235 private static byte[] serializableByteStreamFor(String className, long uid) 236 throws Exception 237 { 238 return byteStreamFor(className, uid, SC_SERIALIZABLE); 239 } 240 241 private static byte[] externalizableByteStreamFor(String className, long uid) 242 throws Exception 243 { 244 return byteStreamFor(className, uid, SC_EXTERNALIZABLE); 245 } 246 247 @DataProvider(name = "classes") 248 public Object[][] classes() { 249 return new Object[][] { 250 new Object[] { NonSerializablePoint.class }, 251 new Object[] { SerializablePoint.class }, 252 new Object[] { SerializablePrimitivePoint.class } 253 }; 254 } 255 256 static final Class<InvalidClassException> ICE = InvalidClassException.class; 257 258 // value class read directly from a byte stream, both serializable and externalizable are supported 259 @Test(dataProvider = "classes") 260 public void deserialize(Class<?> cls) throws Exception { 261 var clsDesc = ObjectStreamClass.lookup(cls); 262 long uid = clsDesc == null ? 0L : clsDesc.getSerialVersionUID(); 263 264 byte[] serialBytes = serializableByteStreamFor(cls.getName(), uid); 265 266 byte[] extBytes = externalizableByteStreamFor(cls.getName(), uid); 267 } 268 269 static <T> byte[] serialize(T obj) throws IOException { 270 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 271 ObjectOutputStream oos = new ObjectOutputStream(baos); 272 oos.writeObject(obj); 273 oos.close(); 274 return baos.toByteArray(); 275 } 276 277 @SuppressWarnings("unchecked") 278 static <T> T deserialize(byte[] streamBytes) 279 throws IOException, ClassNotFoundException 280 { 281 ByteArrayInputStream bais = new ByteArrayInputStream(streamBytes); 282 ObjectInputStream ois = new ObjectInputStream(bais); 283 return (T) ois.readObject(); 284 } 285 286 @SuppressWarnings("unchecked") 287 static <T> T serializeDeserialize(T obj) 288 throws IOException, ClassNotFoundException 289 { 290 return (T) deserialize(serialize(obj)); 291 } 292 }