8254234: Add test library stream object builder

Reviewed-by: rriggs
This commit is contained in:
Chris Hegarty 2020-10-12 13:06:10 +00:00
parent 4184959d85
commit aad3cf4df7
2 changed files with 229 additions and 189 deletions

View File

@ -24,37 +24,25 @@
/*
* @test
* @summary Checks that the appropriate value is given to the canonical ctr
* @library /test/lib
* @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 org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static java.io.ObjectStreamConstants.SC_SERIALIZABLE;
import static java.io.ObjectStreamConstants.STREAM_MAGIC;
import static java.io.ObjectStreamConstants.STREAM_VERSION;
import static java.io.ObjectStreamConstants.TC_CLASSDESC;
import static java.io.ObjectStreamConstants.TC_ENDBLOCKDATA;
import static java.io.ObjectStreamConstants.TC_NULL;
import static java.io.ObjectStreamConstants.TC_OBJECT;
import static java.io.ObjectStreamConstants.TC_STRING;
import static java.lang.System.out;
import static org.testng.Assert.assertEquals;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InvalidClassException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.UncheckedIOException;
import java.util.LinkedHashMap;
import java.util.Map;
import jdk.test.lib.serial.SerialObjectBuilder;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static java.lang.System.out;
import static org.testng.Assert.assertEquals;
/**
* Checks that the appropriate value is given to the canonical ctr.
@ -114,7 +102,7 @@ public class DifferentStreamFieldsTest {
throws Exception {
out.println("\n---");
assert clazz.isRecord();
byte[] bytes = SerialByteStreamBuilder
byte[] bytes = SerialObjectBuilder
.newBuilder(clazz.getName())
.build();
@ -123,7 +111,7 @@ public class DifferentStreamFieldsTest {
Object actualXValue = clazz.getDeclaredMethod("x").invoke(obj);
assertEquals(actualXValue, expectedXValue);
bytes = SerialByteStreamBuilder
bytes = SerialObjectBuilder
.newBuilder(clazz.getName())
.addPrimitiveField("y", int.class, 5) // stream junk
.build();
@ -143,7 +131,7 @@ public class DifferentStreamFieldsTest {
double h, Object i, String j, long[]k, Object[]l)
implements Serializable {}
byte[] bytes = SerialByteStreamBuilder
byte[] bytes = SerialObjectBuilder
.newBuilder(R15.class.getName())
.addPrimitiveField("x", int.class, 5) // stream junk
.build();
@ -173,7 +161,7 @@ public class DifferentStreamFieldsTest {
var r = new R(5);
byte[] OOSBytes = serialize(r);
byte[] builderBytes = SerialByteStreamBuilder
byte[] builderBytes = SerialObjectBuilder
.newBuilder(R.class.getName())
.addPrimitiveField("x", int.class, 5)
.build();
@ -191,7 +179,7 @@ public class DifferentStreamFieldsTest {
var deser1 = deserialize(OOSBytes);
assertEquals(deser1, r);
byte[] builderBytes = SerialByteStreamBuilder
byte[] builderBytes = SerialObjectBuilder
.newBuilder(R.class.getName())
.addPrimitiveField("x", int.class, 7)
.addPrimitiveField("y", int.class, 8)
@ -200,7 +188,7 @@ public class DifferentStreamFieldsTest {
var deser2 = deserialize(builderBytes);
assertEquals(deser2, deser1);
builderBytes = SerialByteStreamBuilder
builderBytes = SerialObjectBuilder
.newBuilder(R.class.getName())
.addPrimitiveField("y", int.class, 8) // reverse order
.addPrimitiveField("x", int.class, 7)
@ -208,7 +196,7 @@ public class DifferentStreamFieldsTest {
deser2 = deserialize(builderBytes);
assertEquals(deser2, deser1);
builderBytes = SerialByteStreamBuilder
builderBytes = SerialObjectBuilder
.newBuilder(R.class.getName())
.addPrimitiveField("w", int.class, 6) // additional fields
.addPrimitiveField("x", int.class, 7)
@ -223,7 +211,7 @@ public class DifferentStreamFieldsTest {
deser1 = deserialize(OOSBytes);
assertEquals(deser1, r);
builderBytes = SerialByteStreamBuilder
builderBytes = SerialObjectBuilder
.newBuilder(R.class.getName())
.addPrimitiveField("y", int.class, 0)
.addPrimitiveField("x", int.class, 0)
@ -231,7 +219,7 @@ public class DifferentStreamFieldsTest {
deser2 = deserialize(builderBytes);
assertEquals(deser2, deser1);
builderBytes = SerialByteStreamBuilder
builderBytes = SerialObjectBuilder
.newBuilder(R.class.getName()) // no field values
.build();
deser2 = deserialize(builderBytes);
@ -249,7 +237,7 @@ public class DifferentStreamFieldsTest {
var deser1 = deserialize(serialize(r));
assertEquals(deser1, r);
byte[] builderBytes = SerialByteStreamBuilder
byte[] builderBytes = SerialObjectBuilder
.newBuilder(Str.class.getName())
.addField("part1", String.class, "Hello")
.addField("part2", String.class, "World!")
@ -258,7 +246,7 @@ public class DifferentStreamFieldsTest {
var deser2 = deserialize(builderBytes);
assertEquals(deser2, deser1);
builderBytes = SerialByteStreamBuilder
builderBytes = SerialObjectBuilder
.newBuilder(Str.class.getName())
.addField("cruft", String.class, "gg")
.addField("part1", String.class, "Hello")
@ -280,7 +268,7 @@ public class DifferentStreamFieldsTest {
assertEquals(deser1.ints(), r.ints());
assertEquals(deser1.longs(), r.longs());
byte[] builderBytes = SerialByteStreamBuilder
byte[] builderBytes = SerialObjectBuilder
.newBuilder(IntArray.class.getName())
.addField("ints", int[].class, new int[]{5, 4, 3, 2, 1})
.addField("longs", long[].class, new long[]{9L})
@ -296,7 +284,7 @@ public class DifferentStreamFieldsTest {
StrArray deser1 = deserialize(serialize(r));
assertEquals(deser1.stringArray(), r.stringArray());
byte[] builderBytes = SerialByteStreamBuilder
byte[] builderBytes = SerialObjectBuilder
.newBuilder(StrArray.class.getName())
.addField("stringArray", String[].class, new String[]{"foo", "bar"})
.build();
@ -317,7 +305,7 @@ public class DifferentStreamFieldsTest {
var deser1 = deserialize(serialize(r));
assertEquals(deser1, r);
byte[] builderBytes = SerialByteStreamBuilder
byte[] builderBytes = SerialObjectBuilder
.newBuilder(NumberHolder.class.getName())
.addField("n", Integer.class, 123)
.build();
@ -333,7 +321,7 @@ public class DifferentStreamFieldsTest {
var deser1 = deserialize(serialize(r));
assertEquals(deser1, r);
byte[] builderBytes = SerialByteStreamBuilder
byte[] builderBytes = SerialObjectBuilder
.newBuilder(IntegerHolder.class.getName())
.addField("i", Number.class, 123)
.build();
@ -353,7 +341,7 @@ public class DifferentStreamFieldsTest {
var deser1 = deserialize(serialize(r));
assertEquals(deser1, r);
byte[] builderBytes = SerialByteStreamBuilder
byte[] builderBytes = SerialObjectBuilder
.newBuilder(StringHolder.class.getName())
.addField("s", Integer.class, 123)
.build();
@ -377,7 +365,7 @@ public class DifferentStreamFieldsTest {
var deser1 = deserialize(serialize(r));
assertEquals(deser1, r);
byte[] builderBytes = SerialByteStreamBuilder
byte[] builderBytes = SerialObjectBuilder
.newBuilder(IntHolder.class.getName())
.addPrimitiveField("i", long.class, 123L)
.build();
@ -406,158 +394,4 @@ public class DifferentStreamFieldsTest {
ObjectInputStream ois = new ObjectInputStream(bais);
return (T) ois.readObject();
}
static class SerialByteStreamBuilder {
private final ObjectOutputStream objectOutputStream;
private final ByteArrayOutputStream byteArrayOutputStream;
record NameAndType<T>(String name, Class<T>type) {}
private String className;
private final LinkedHashMap<NameAndType<?>, Object> primFields = new LinkedHashMap<>();
private final LinkedHashMap<NameAndType<?>, Object> objectFields = new LinkedHashMap<>();
private SerialByteStreamBuilder() {
try {
byteArrayOutputStream = new ByteArrayOutputStream();
objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public static SerialByteStreamBuilder newBuilder(String className) {
return (new SerialByteStreamBuilder()).className(className);
}
private SerialByteStreamBuilder className(String className) {
this.className = className;
return this;
}
public <T> SerialByteStreamBuilder addPrimitiveField(String name, Class<T> type, T value) {
if (!type.isPrimitive())
throw new IllegalArgumentException("Unexpected non-primitive field: " + type);
primFields.put(new NameAndType<>(name, type), value);
return this;
}
public <T> SerialByteStreamBuilder addField(String name, Class<T> type, T 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<NameAndType<?>, Object> 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<NameAndType<?>, Object> 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<NameAndType<?>, Object> 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,
(cl.isArray() ? cl.getName() : "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<NameAndType<?>, Object> 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();
}
public 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);
}
}
}
}

View File

@ -0,0 +1,206 @@
/*
* Copyright (c) 2019, 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.
*/
package jdk.test.lib.serial;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.UncheckedIOException;
import java.util.LinkedHashMap;
import java.util.Map;
import static java.io.ObjectStreamConstants.*;
/**
* A basic builder of a serial object.
*/
public class SerialObjectBuilder {
private final ObjectOutputStream objectOutputStream;
private final ByteArrayOutputStream byteArrayOutputStream;
private record NameAndType<T>(String name, Class<T>type) { }
private String className;
private long suid;
private SerialObjectBuilder superClass;
private final LinkedHashMap<NameAndType<?>, Object> primFields = new LinkedHashMap<>();
private final LinkedHashMap<NameAndType<?>, Object> objectFields = new LinkedHashMap<>();
private SerialObjectBuilder() {
try {
byteArrayOutputStream = new ByteArrayOutputStream();
objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public static SerialObjectBuilder newBuilder(String className) {
return (new SerialObjectBuilder()).className(className);
}
private SerialObjectBuilder className(String className) {
this.className = className;
return this;
}
public SerialObjectBuilder suid(long suid) {
this.suid = suid;
return this;
}
public SerialObjectBuilder superClass(SerialObjectBuilder superClass) {
this.superClass = superClass;
return this;
}
public <T> SerialObjectBuilder addPrimitiveField(String name, Class<T> type, T value) {
if (!type.isPrimitive())
throw new IllegalArgumentException("Unexpected non-primitive field: " + type);
primFields.put(new NameAndType<>(name, type), value);
return this;
}
public <T> SerialObjectBuilder addField(String name, Class<T> type, T value) {
if (type.isPrimitive())
throw new IllegalArgumentException("Unexpected primitive field: " + type);
objectFields.put(new NameAndType<>(name, type), value);
return this;
}
private static void writeUTF(DataOutputStream out, String str) throws IOException {
assert str.codePoints().noneMatch(cp -> cp > 127); // only ASCII for now
int utflen = str.length();
assert utflen <= 0xFFFF; // only small strings for now
out.writeShort(utflen);
out.writeBytes(str);
}
private void writePrimFieldsDesc(DataOutputStream out) throws IOException {
for (Map.Entry<NameAndType<?>, Object> entry : primFields.entrySet()) {
Class<?> primClass = entry.getKey().type();
assert primClass.isPrimitive();
assert primClass != void.class;
out.writeByte(primClass.descriptorString().getBytes()[0]); // prim_typecode
out.writeUTF(entry.getKey().name()); // fieldName
}
}
private void writePrimFieldsValues(DataOutputStream out) throws IOException {
for (Map.Entry<NameAndType<?>, Object> 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<NameAndType<?>, Object> 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, cl.descriptorString());
}
}
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<NameAndType<?>, Object> 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();
}
private static void writeClassDesc(DataOutputStream dos,
SerialObjectBuilder sb)
throws IOException
{
dos.writeByte(TC_CLASSDESC);
dos.writeUTF(sb.className);
dos.writeLong(sb.suid);
dos.writeByte(SC_SERIALIZABLE);
dos.writeShort(sb.numFields()); // number of fields
sb.writePrimFieldsDesc(dos);
sb.writeObjectFieldDesc(dos);
dos.writeByte(TC_ENDBLOCKDATA); // no annotations
if (sb.superClass == null) {
dos.writeByte(TC_NULL); // no superclasses
} else {
writeClassDesc(dos, sb.superClass);
}
}
public byte[] build() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeShort(STREAM_MAGIC);
dos.writeShort(STREAM_VERSION);
dos.writeByte(TC_OBJECT);
writeClassDesc(dos, this);
if (superClass != null) {
superClass.writePrimFieldsValues(dos);
superClass.writeObjectFieldValues(dos);
}
writePrimFieldsValues(dos);
writeObjectFieldValues(dos);
dos.close();
return baos.toByteArray();
} catch (IOException unexpected) {
throw new AssertionError(unexpected);
}
}
}