1 /* 2 * Copyright (c) 2020, 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 Checks that the appropriate value is given to the canonical ctr 27 * @compile --enable-preview -source ${jdk.version} DifferentStreamFieldsTest.java 28 * @run testng/othervm --enable-preview DifferentStreamFieldsTest 29 * @run testng/othervm/java.security.policy=empty_security.policy --enable-preview DifferentStreamFieldsTest 30 */ 31 32 import java.io.ByteArrayInputStream; 33 import java.io.ByteArrayOutputStream; 34 import java.io.DataOutputStream; 35 import java.io.IOException; 36 import java.io.ObjectInputStream; 37 import java.io.ObjectOutputStream; 38 import java.io.Serializable; 39 import java.util.LinkedHashMap; 40 import java.util.Map; 41 import org.testng.annotations.DataProvider; 42 import org.testng.annotations.Test; 43 import static java.io.ObjectStreamConstants.*; 44 import static java.lang.System.out; 45 import static org.testng.Assert.*; 46 47 /** 48 * ... 49 */ 50 public class DifferentStreamFieldsTest { 51 52 record R01(boolean x) implements Serializable { } 53 record R02(byte x) implements Serializable { } 54 record R03(short x) implements Serializable { } 55 record R04(char x) implements Serializable { } 56 record R05(int x) implements Serializable { } 57 record R06(long x) implements Serializable { } 58 record R07(float x) implements Serializable { } 59 record R08(double x) implements Serializable { } 60 record R09(Object x) implements Serializable { } 61 record R10(String x) implements Serializable { } 62 record R11(int[] x) implements Serializable { } 63 record R12(Object[] x) implements Serializable { } 64 record R13(R12 x) implements Serializable { } 65 record R14(R13[] x) implements Serializable { } 66 67 @DataProvider(name = "recordTypeAndExpectedValue") 68 public Object[][] recordTypeAndExpectedValue() { 69 return new Object[][] { 70 new Object[] { R01.class, false }, 71 new Object[] { R02.class, (byte)0 }, 72 new Object[] { R03.class, (short)0 }, 73 new Object[] { R04.class, '\u0000' }, 74 new Object[] { R05.class, 0 }, 75 new Object[] { R06.class, 0L }, 76 new Object[] { R07.class, 0.0f }, 77 new Object[] { R08.class, 0.0d }, 78 new Object[] { R09.class, null }, 79 new Object[] { R10.class, null }, 80 new Object[] { R11.class, null }, 81 new Object[] { R12.class, null }, 82 new Object[] { R13.class, null }, 83 new Object[] { R14.class, null }, 84 }; 85 } 86 87 @Test(dataProvider = "recordTypeAndExpectedValue") 88 public void testWithDifferentTypes(Class<?> clazz, Object expectedXValue) 89 throws Exception 90 { 91 out.println("\n---"); 92 assert clazz.isRecord(); 93 byte[] bytes = SerialByteStreamBuilder 94 .newBuilder(clazz.getName()) 95 .build(); 96 97 Object obj = deserialize(bytes); 98 out.println("deserialized: " + obj); 99 Object actualXValue = clazz.getDeclaredMethod("x").invoke(obj); 100 assertEquals(actualXValue, expectedXValue); 101 102 bytes = SerialByteStreamBuilder 103 .newBuilder(clazz.getName()) 104 .addPrimitiveField("y", int.class, 5) // stream junk 105 .build(); 106 107 obj = deserialize(bytes); 108 out.println("deserialized: " + obj); 109 actualXValue = clazz.getDeclaredMethod("x").invoke(obj); 110 assertEquals(actualXValue, expectedXValue); 111 } 112 113 // --- all together 114 115 @Test 116 public void testWithAllTogether() throws Exception { 117 out.println("\n---"); 118 record R15(boolean a, byte b, short c, char d, int e, long f, float g, double h, Object i, String j, long[] k, Object[] l) 119 implements Serializable { } 120 121 byte[] bytes = SerialByteStreamBuilder 122 .newBuilder(R15.class.getName()) 123 .addPrimitiveField("x", int.class, 5) // stream junk 124 .build(); 125 126 R15 obj = deserialize(bytes); 127 out.println("deserialized: " + obj); 128 assertEquals(obj.a, false); 129 assertEquals(obj.b, 0); 130 assertEquals(obj.c, 0); 131 assertEquals(obj.d, '\u0000'); 132 assertEquals(obj.e, 0); 133 assertEquals(obj.f, 0l); 134 assertEquals(obj.g, 0f); 135 assertEquals(obj.h, 0d); 136 assertEquals(obj.i, null); 137 assertEquals(obj.j, null); 138 assertEquals(obj.k, null); 139 assertEquals(obj.l, null); 140 } 141 142 @Test 143 public void testInt() throws Exception { 144 out.println("\n---"); 145 { 146 record R(int x) implements Serializable { } 147 148 var r = new R(5); 149 byte[] OOSBytes = serialize(r); 150 151 byte[] builderBytes = SerialByteStreamBuilder 152 .newBuilder(R.class.getName()) 153 .addPrimitiveField("x", int.class, 5) 154 .build(); 155 156 var deser1 = deserialize(OOSBytes); 157 var deser2 = deserialize(builderBytes); 158 assertEquals(deser2, deser1); 159 } 160 { 161 record R(int x, int y) implements Serializable { } 162 163 var r = new R(7, 8); 164 byte[] OOSBytes = serialize(r); 165 var deser1 = deserialize(OOSBytes); 166 167 byte[] builderBytes = SerialByteStreamBuilder 168 .newBuilder(R.class.getName()) 169 .addPrimitiveField("x", int.class, 7) 170 .addPrimitiveField("y", int.class, 8) 171 .build(); 172 173 var deser2 = deserialize(builderBytes); 174 assertEquals(deser2, deser1); 175 176 builderBytes = SerialByteStreamBuilder 177 .newBuilder(R.class.getName()) 178 .addPrimitiveField("y", int.class, 8) // reverse order 179 .addPrimitiveField("x", int.class, 7) 180 .build(); 181 deser2 = deserialize(builderBytes); 182 assertEquals(deser2, deser1); 183 184 builderBytes = SerialByteStreamBuilder 185 .newBuilder(R.class.getName()) 186 .addPrimitiveField("w", int.class, 6) // additional fields 187 .addPrimitiveField("x", int.class, 7) 188 .addPrimitiveField("y", int.class, 8) 189 .addPrimitiveField("z", int.class, 9) // additional fields 190 .build(); 191 deser2 = deserialize(builderBytes); 192 assertEquals(deser2, deser1); 193 194 r = new R(0, 0); 195 OOSBytes = serialize(r); 196 deser1 = deserialize(OOSBytes); 197 198 builderBytes = SerialByteStreamBuilder 199 .newBuilder(R.class.getName()) 200 .addPrimitiveField("y", int.class, 0) 201 .addPrimitiveField("x", int.class, 0) 202 .build(); 203 deser2 = deserialize(builderBytes); 204 assertEquals(deser2, deser1); 205 206 builderBytes = SerialByteStreamBuilder 207 .newBuilder(R.class.getName()) // no field values 208 .build(); 209 deser2 = deserialize(builderBytes); 210 assertEquals(deser2, deser1); 211 } 212 } 213 214 @Test 215 public void testString() throws Exception { 216 out.println("\n---"); 217 218 record Str(String part1, String part2) implements Serializable { } 219 220 var r = new Str("Hello", "World!"); 221 var deser1 = deserialize(serialize(r)); 222 223 byte[] builderBytes = SerialByteStreamBuilder 224 .newBuilder(Str.class.getName()) 225 .addField("part1", String.class, "Hello") 226 .addField("part2", String.class, "World!") 227 .build(); 228 229 var deser2 = deserialize(builderBytes); 230 assertEquals(deser2, deser1); 231 232 builderBytes = SerialByteStreamBuilder 233 .newBuilder(Str.class.getName()) 234 .addField("cruft", String.class, "gg") 235 .addField("part1", String.class, "Hello") 236 .addField("part2", String.class, "World!") 237 .addPrimitiveField("x", int.class, 13) 238 .build(); 239 240 var deser3 = deserialize(builderBytes); 241 assertEquals(deser3, deser1); 242 } 243 244 @Test 245 public void testArrays() throws Exception { 246 out.println("\n---"); 247 { 248 record IntArray(int[] ints, long[] longs) implements Serializable { } 249 IntArray r = new IntArray(new int[] { 5, 4, 3,2, 1 }, new long[] { 9L }); 250 IntArray deser1 = deserialize(serialize(r)); 251 252 byte[] builderBytes = SerialByteStreamBuilder 253 .newBuilder(IntArray.class.getName()) 254 .addField("ints", String[].class, new int[] { 5, 4, 3,2, 1 }) 255 .addField("longs", String[].class, new long[] { 9L }) 256 .build(); 257 258 IntArray deser2 = deserialize(builderBytes); 259 assertEquals(deser2.ints(), deser1.ints()); 260 assertEquals(deser2.longs(), deser1.longs()); 261 } 262 { 263 record StrArray(String[] stringArray) implements Serializable { } 264 StrArray r = new StrArray(new String[] { "foo", "bar" }); 265 StrArray deser1 = deserialize(serialize(r)); 266 267 byte[] builderBytes = SerialByteStreamBuilder 268 .newBuilder(StrArray.class.getName()) 269 .addField("stringArray", String[].class, new String[] { "foo", "bar" }) 270 .build(); 271 272 StrArray deser2 = deserialize(builderBytes); 273 assertEquals(deser2.stringArray(), deser1.stringArray()); 274 } 275 } 276 277 static class SerialByteStreamBuilder { 278 279 private final ObjectOutputStream objectOutputStream; 280 private final ByteArrayOutputStream byteArrayOutputStream; 281 282 record NameAndType<T> (String name, Class<T> type) { } 283 284 private String className; 285 private LinkedHashMap<NameAndType,Object> primFields = new LinkedHashMap<>(); 286 private LinkedHashMap<NameAndType,Object> objectFields = new LinkedHashMap<>(); 287 288 private SerialByteStreamBuilder() { 289 try { 290 byteArrayOutputStream = new ByteArrayOutputStream(); 291 objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); 292 } catch (IOException e) { 293 throw new AssertionError(e); 294 } 295 } 296 297 static SerialByteStreamBuilder newBuilder(String className) { 298 return (new SerialByteStreamBuilder()).className(className); 299 } 300 301 private SerialByteStreamBuilder className(String className) { 302 this.className = className; 303 return this; 304 } 305 306 SerialByteStreamBuilder addPrimitiveField(String name, Class<?> type, Object value) { 307 if (!type.isPrimitive()) 308 throw new IllegalArgumentException("Unexpected non-primitive field: " + type); 309 primFields.put(new NameAndType(name, type), value); 310 return this; 311 } 312 313 SerialByteStreamBuilder addField(String name, Class<?> type, Object value) { 314 if (type.isPrimitive()) 315 throw new IllegalArgumentException("Unexpected primitive field: " + type); 316 objectFields.put(new NameAndType(name, type), value); 317 return this; 318 } 319 320 private static int getPrimitiveSignature(Class<?> cl) { 321 if (cl == Integer.TYPE) return 'I'; 322 else if (cl == Byte.TYPE) return 'B'; 323 else if (cl == Long.TYPE) return 'J'; 324 else if (cl == Float.TYPE) return 'F'; 325 else if (cl == Double.TYPE) return 'D'; 326 else if (cl == Short.TYPE) return 'S'; 327 else if (cl == Character.TYPE) return 'C'; 328 else if (cl == Boolean.TYPE) return 'Z'; 329 else throw new InternalError(); 330 } 331 332 private static void writeUTF(DataOutputStream out, String str) throws IOException { 333 int utflen = str.length(); // assume ASCII 334 assert utflen <= 0xFFFF; 335 out.writeShort(utflen); 336 out.writeBytes(str); 337 } 338 339 private void writePrimFieldsDesc(DataOutputStream out) throws IOException { 340 for (Map.Entry<NameAndType,Object> entry : primFields.entrySet()) { 341 assert entry.getKey().type() != void.class; 342 out.writeByte(getPrimitiveSignature(entry.getKey().type())); // prim_typecode 343 out.writeUTF(entry.getKey().name()); // fieldName 344 } 345 } 346 347 private void writePrimFieldsValues(DataOutputStream out) throws IOException { 348 for (Map.Entry<NameAndType,Object> entry : primFields.entrySet()) { 349 Class<?> cl = entry.getKey().type(); 350 Object value = entry.getValue(); 351 if (cl == Integer.TYPE) out.writeInt((int) value); 352 else if (cl == Byte.TYPE) out.writeByte((byte) value); 353 else if (cl == Long.TYPE) out.writeLong((long) value); 354 else if (cl == Float.TYPE) out.writeFloat((float) value); 355 else if (cl == Double.TYPE) out.writeDouble((double) value); 356 else if (cl == Short.TYPE) out.writeShort((short) value); 357 else if (cl == Character.TYPE) out.writeChar((char) value); 358 else if (cl == Boolean.TYPE) out.writeBoolean((boolean) value); 359 else throw new InternalError(); 360 } 361 } 362 363 private void writeObjectFieldDesc(DataOutputStream out) throws IOException { 364 for (Map.Entry<NameAndType,Object> entry : objectFields.entrySet()) { 365 Class<?> cl = entry.getKey().type(); 366 assert !cl.isPrimitive(); 367 // obj_typecode 368 if (cl.isArray()) { 369 out.writeByte('['); 370 } else { 371 out.writeByte('L'); 372 } 373 writeUTF(out, entry.getKey().name()); 374 out.writeByte(TC_STRING); 375 writeUTF(out, "L" + cl.getName().replace('.', '/') + ";"); 376 377 } 378 } 379 380 private void writeObject(DataOutputStream out, Object value) throws IOException { 381 objectOutputStream.reset(); 382 byteArrayOutputStream.reset(); 383 objectOutputStream.writeUnshared(value); 384 out.write(byteArrayOutputStream.toByteArray()); 385 } 386 387 private void writeObjectFieldValues(DataOutputStream out) throws IOException { 388 for (Map.Entry<NameAndType,Object> entry : objectFields.entrySet()) { 389 Class<?> cl = entry.getKey().type(); 390 assert !cl.isPrimitive(); 391 if (cl == String.class) { 392 out.writeByte(TC_STRING); 393 writeUTF(out, (String) entry.getValue()); 394 } else { 395 writeObject(out, entry.getValue()); 396 } 397 } 398 } 399 400 private int numFields() { 401 return primFields.size() + objectFields.size(); 402 } 403 404 byte[] build() { 405 try { 406 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 407 DataOutputStream dos = new DataOutputStream(baos); 408 dos.writeShort(STREAM_MAGIC); 409 dos.writeShort(STREAM_VERSION); 410 dos.writeByte(TC_OBJECT); 411 dos.writeByte(TC_CLASSDESC); 412 dos.writeUTF(className); 413 dos.writeLong(0L); 414 dos.writeByte(SC_SERIALIZABLE); 415 dos.writeShort(numFields()); // number of fields 416 writePrimFieldsDesc(dos); 417 writeObjectFieldDesc(dos); 418 dos.writeByte(TC_ENDBLOCKDATA); // no annotations 419 dos.writeByte(TC_NULL); // no superclasses 420 writePrimFieldsValues(dos); 421 writeObjectFieldValues(dos); 422 dos.close(); 423 return baos.toByteArray(); 424 } catch (IOException unexpected) { 425 throw new AssertionError(unexpected); 426 } 427 } 428 } 429 430 <T> byte[] serialize(T obj) throws IOException { 431 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 432 ObjectOutputStream oos = new ObjectOutputStream(baos); 433 oos.writeObject(obj); 434 oos.close(); 435 return baos.toByteArray(); 436 } 437 438 @SuppressWarnings("unchecked") 439 static <T> T deserialize(byte[] streamBytes) 440 throws IOException, ClassNotFoundException 441 { 442 ByteArrayInputStream bais = new ByteArrayInputStream(streamBytes); 443 ObjectInputStream ois = new ObjectInputStream(bais); 444 return (T) ois.readObject(); 445 } 446 }