--- /dev/null 2020-06-17 15:02:28.000000000 +0100 +++ new/test/jdk/java/io/Serializable/records/DifferentStreamFieldsTest.java 2020-06-17 15:02:27.000000000 +0100 @@ -0,0 +1,446 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Checks that the appropriate value is given to the canonical ctr + * @compile --enable-preview -source ${jdk.version} DifferentStreamFieldsTest.java + * @run testng/othervm --enable-preview DifferentStreamFieldsTest + * @run testng/othervm/java.security.policy=empty_security.policy --enable-preview DifferentStreamFieldsTest + */ + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import static java.io.ObjectStreamConstants.*; +import static java.lang.System.out; +import static org.testng.Assert.*; + +/** + * ... + */ +public class DifferentStreamFieldsTest { + + record R01(boolean x) implements Serializable { } + record R02(byte x) implements Serializable { } + record R03(short x) implements Serializable { } + record R04(char x) implements Serializable { } + record R05(int x) implements Serializable { } + record R06(long x) implements Serializable { } + record R07(float x) implements Serializable { } + record R08(double x) implements Serializable { } + record R09(Object x) implements Serializable { } + record R10(String x) implements Serializable { } + record R11(int[] x) implements Serializable { } + record R12(Object[] x) implements Serializable { } + record R13(R12 x) implements Serializable { } + record R14(R13[] x) implements Serializable { } + + @DataProvider(name = "recordTypeAndExpectedValue") + public Object[][] recordTypeAndExpectedValue() { + return new Object[][] { + new Object[] { R01.class, false }, + new Object[] { R02.class, (byte)0 }, + new Object[] { R03.class, (short)0 }, + new Object[] { R04.class, '\u0000' }, + new Object[] { R05.class, 0 }, + new Object[] { R06.class, 0L }, + new Object[] { R07.class, 0.0f }, + new Object[] { R08.class, 0.0d }, + new Object[] { R09.class, null }, + new Object[] { R10.class, null }, + new Object[] { R11.class, null }, + new Object[] { R12.class, null }, + new Object[] { R13.class, null }, + new Object[] { R14.class, null }, + }; + } + + @Test(dataProvider = "recordTypeAndExpectedValue") + public void testWithDifferentTypes(Class clazz, Object expectedXValue) + throws Exception + { + out.println("\n---"); + assert clazz.isRecord(); + byte[] bytes = SerialByteStreamBuilder + .newBuilder(clazz.getName()) + .build(); + + Object obj = deserialize(bytes); + out.println("deserialized: " + obj); + Object actualXValue = clazz.getDeclaredMethod("x").invoke(obj); + assertEquals(actualXValue, expectedXValue); + + bytes = SerialByteStreamBuilder + .newBuilder(clazz.getName()) + .addPrimitiveField("y", int.class, 5) // stream junk + .build(); + + obj = deserialize(bytes); + out.println("deserialized: " + obj); + actualXValue = clazz.getDeclaredMethod("x").invoke(obj); + assertEquals(actualXValue, expectedXValue); + } + + // --- all together + + @Test + public void testWithAllTogether() throws Exception { + out.println("\n---"); + 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) + implements Serializable { } + + byte[] bytes = SerialByteStreamBuilder + .newBuilder(R15.class.getName()) + .addPrimitiveField("x", int.class, 5) // stream junk + .build(); + + R15 obj = deserialize(bytes); + out.println("deserialized: " + obj); + assertEquals(obj.a, false); + assertEquals(obj.b, 0); + assertEquals(obj.c, 0); + assertEquals(obj.d, '\u0000'); + assertEquals(obj.e, 0); + assertEquals(obj.f, 0l); + assertEquals(obj.g, 0f); + assertEquals(obj.h, 0d); + assertEquals(obj.i, null); + assertEquals(obj.j, null); + assertEquals(obj.k, null); + assertEquals(obj.l, null); + } + + @Test + public void testInt() throws Exception { + out.println("\n---"); + { + record R(int x) implements Serializable { } + + var r = new R(5); + byte[] OOSBytes = serialize(r); + + byte[] builderBytes = SerialByteStreamBuilder + .newBuilder(R.class.getName()) + .addPrimitiveField("x", int.class, 5) + .build(); + + var deser1 = deserialize(OOSBytes); + var deser2 = deserialize(builderBytes); + assertEquals(deser2, deser1); + } + { + record R(int x, int y) implements Serializable { } + + var r = new R(7, 8); + byte[] OOSBytes = serialize(r); + var deser1 = deserialize(OOSBytes); + + byte[] builderBytes = SerialByteStreamBuilder + .newBuilder(R.class.getName()) + .addPrimitiveField("x", int.class, 7) + .addPrimitiveField("y", int.class, 8) + .build(); + + var deser2 = deserialize(builderBytes); + assertEquals(deser2, deser1); + + builderBytes = SerialByteStreamBuilder + .newBuilder(R.class.getName()) + .addPrimitiveField("y", int.class, 8) // reverse order + .addPrimitiveField("x", int.class, 7) + .build(); + deser2 = deserialize(builderBytes); + assertEquals(deser2, deser1); + + builderBytes = SerialByteStreamBuilder + .newBuilder(R.class.getName()) + .addPrimitiveField("w", int.class, 6) // additional fields + .addPrimitiveField("x", int.class, 7) + .addPrimitiveField("y", int.class, 8) + .addPrimitiveField("z", int.class, 9) // additional fields + .build(); + deser2 = deserialize(builderBytes); + assertEquals(deser2, deser1); + + r = new R(0, 0); + OOSBytes = serialize(r); + deser1 = deserialize(OOSBytes); + + builderBytes = SerialByteStreamBuilder + .newBuilder(R.class.getName()) + .addPrimitiveField("y", int.class, 0) + .addPrimitiveField("x", int.class, 0) + .build(); + deser2 = deserialize(builderBytes); + assertEquals(deser2, deser1); + + builderBytes = SerialByteStreamBuilder + .newBuilder(R.class.getName()) // no field values + .build(); + deser2 = deserialize(builderBytes); + assertEquals(deser2, deser1); + } + } + + @Test + public void testString() throws Exception { + out.println("\n---"); + + record Str(String part1, String part2) implements Serializable { } + + var r = new Str("Hello", "World!"); + var deser1 = deserialize(serialize(r)); + + byte[] builderBytes = SerialByteStreamBuilder + .newBuilder(Str.class.getName()) + .addField("part1", String.class, "Hello") + .addField("part2", String.class, "World!") + .build(); + + var deser2 = deserialize(builderBytes); + assertEquals(deser2, deser1); + + builderBytes = SerialByteStreamBuilder + .newBuilder(Str.class.getName()) + .addField("cruft", String.class, "gg") + .addField("part1", String.class, "Hello") + .addField("part2", String.class, "World!") + .addPrimitiveField("x", int.class, 13) + .build(); + + var deser3 = deserialize(builderBytes); + assertEquals(deser3, deser1); + } + + @Test + public void testArrays() throws Exception { + out.println("\n---"); + { + record IntArray(int[] ints, long[] longs) implements Serializable { } + IntArray r = new IntArray(new int[] { 5, 4, 3,2, 1 }, new long[] { 9L }); + IntArray deser1 = deserialize(serialize(r)); + + byte[] builderBytes = SerialByteStreamBuilder + .newBuilder(IntArray.class.getName()) + .addField("ints", String[].class, new int[] { 5, 4, 3,2, 1 }) + .addField("longs", String[].class, new long[] { 9L }) + .build(); + + IntArray deser2 = deserialize(builderBytes); + assertEquals(deser2.ints(), deser1.ints()); + assertEquals(deser2.longs(), deser1.longs()); + } + { + record StrArray(String[] stringArray) implements Serializable { } + StrArray r = new StrArray(new String[] { "foo", "bar" }); + StrArray deser1 = deserialize(serialize(r)); + + byte[] builderBytes = SerialByteStreamBuilder + .newBuilder(StrArray.class.getName()) + .addField("stringArray", String[].class, new String[] { "foo", "bar" }) + .build(); + + StrArray deser2 = deserialize(builderBytes); + assertEquals(deser2.stringArray(), deser1.stringArray()); + } + } + + static class SerialByteStreamBuilder { + + private final ObjectOutputStream objectOutputStream; + private final ByteArrayOutputStream byteArrayOutputStream; + + record NameAndType (String name, Class type) { } + + private String className; + private LinkedHashMap primFields = new LinkedHashMap<>(); + private LinkedHashMap objectFields = new LinkedHashMap<>(); + + private SerialByteStreamBuilder() { + try { + byteArrayOutputStream = new ByteArrayOutputStream(); + objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + static SerialByteStreamBuilder newBuilder(String className) { + return (new SerialByteStreamBuilder()).className(className); + } + + private SerialByteStreamBuilder className(String className) { + this.className = className; + return this; + } + + SerialByteStreamBuilder addPrimitiveField(String name, Class type, Object value) { + if (!type.isPrimitive()) + throw new IllegalArgumentException("Unexpected non-primitive field: " + type); + primFields.put(new NameAndType(name, type), value); + return this; + } + + SerialByteStreamBuilder addField(String name, Class type, Object value) { + if (type.isPrimitive()) + throw new IllegalArgumentException("Unexpected primitive field: " + type); + objectFields.put(new NameAndType(name, type), value); + return this; + } + + private static int getPrimitiveSignature(Class cl) { + if (cl == Integer.TYPE) return 'I'; + else if (cl == Byte.TYPE) return 'B'; + else if (cl == Long.TYPE) return 'J'; + else if (cl == Float.TYPE) return 'F'; + else if (cl == Double.TYPE) return 'D'; + else if (cl == Short.TYPE) return 'S'; + else if (cl == Character.TYPE) return 'C'; + else if (cl == Boolean.TYPE) return 'Z'; + else throw new InternalError(); + } + + private static void writeUTF(DataOutputStream out, String str) throws IOException { + int utflen = str.length(); // assume ASCII + assert utflen <= 0xFFFF; + out.writeShort(utflen); + out.writeBytes(str); + } + + private void writePrimFieldsDesc(DataOutputStream out) throws IOException { + for (Map.Entry entry : primFields.entrySet()) { + assert entry.getKey().type() != void.class; + out.writeByte(getPrimitiveSignature(entry.getKey().type())); // prim_typecode + out.writeUTF(entry.getKey().name()); // fieldName + } + } + + private void writePrimFieldsValues(DataOutputStream out) throws IOException { + for (Map.Entry entry : primFields.entrySet()) { + Class cl = entry.getKey().type(); + Object value = entry.getValue(); + if (cl == Integer.TYPE) out.writeInt((int) value); + else if (cl == Byte.TYPE) out.writeByte((byte) value); + else if (cl == Long.TYPE) out.writeLong((long) value); + else if (cl == Float.TYPE) out.writeFloat((float) value); + else if (cl == Double.TYPE) out.writeDouble((double) value); + else if (cl == Short.TYPE) out.writeShort((short) value); + else if (cl == Character.TYPE) out.writeChar((char) value); + else if (cl == Boolean.TYPE) out.writeBoolean((boolean) value); + else throw new InternalError(); + } + } + + private void writeObjectFieldDesc(DataOutputStream out) throws IOException { + for (Map.Entry entry : objectFields.entrySet()) { + Class cl = entry.getKey().type(); + assert !cl.isPrimitive(); + // obj_typecode + if (cl.isArray()) { + out.writeByte('['); + } else { + out.writeByte('L'); + } + writeUTF(out, entry.getKey().name()); + out.writeByte(TC_STRING); + writeUTF(out, "L" + cl.getName().replace('.', '/') + ";"); + + } + } + + private void writeObject(DataOutputStream out, Object value) throws IOException { + objectOutputStream.reset(); + byteArrayOutputStream.reset(); + objectOutputStream.writeUnshared(value); + out.write(byteArrayOutputStream.toByteArray()); + } + + private void writeObjectFieldValues(DataOutputStream out) throws IOException { + for (Map.Entry entry : objectFields.entrySet()) { + Class cl = entry.getKey().type(); + assert !cl.isPrimitive(); + if (cl == String.class) { + out.writeByte(TC_STRING); + writeUTF(out, (String) entry.getValue()); + } else { + writeObject(out, entry.getValue()); + } + } + } + + private int numFields() { + return primFields.size() + objectFields.size(); + } + + byte[] build() { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos); + dos.writeShort(STREAM_MAGIC); + dos.writeShort(STREAM_VERSION); + dos.writeByte(TC_OBJECT); + dos.writeByte(TC_CLASSDESC); + dos.writeUTF(className); + dos.writeLong(0L); + dos.writeByte(SC_SERIALIZABLE); + dos.writeShort(numFields()); // number of fields + writePrimFieldsDesc(dos); + writeObjectFieldDesc(dos); + dos.writeByte(TC_ENDBLOCKDATA); // no annotations + dos.writeByte(TC_NULL); // no superclasses + writePrimFieldsValues(dos); + writeObjectFieldValues(dos); + dos.close(); + return baos.toByteArray(); + } catch (IOException unexpected) { + throw new AssertionError(unexpected); + } + } + } + + byte[] serialize(T obj) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(obj); + oos.close(); + return baos.toByteArray(); + } + + @SuppressWarnings("unchecked") + static T deserialize(byte[] streamBytes) + throws IOException, ClassNotFoundException + { + ByteArrayInputStream bais = new ByteArrayInputStream(streamBytes); + ObjectInputStream ois = new ObjectInputStream(bais); + return (T) ois.readObject(); + } +}