8263320: [test] Add Object Stream Formatter to work with test utility HexPrinter
Reviewed-by: chegar
This commit is contained in:
parent
fa0f1614ff
commit
788e30c154
347
test/lib-test/jdk/test/lib/hexdump/ObjectStreamPrinterTest.java
Normal file
347
test/lib-test/jdk/test/lib/hexdump/ObjectStreamPrinterTest.java
Normal file
@ -0,0 +1,347 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2021, 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.hexdump;
|
||||
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Check ObjectStreamPrinter formatting
|
||||
* @library /test/lib
|
||||
* @run testng/othervm -DDEBUG=true jdk.test.lib.hexdump.ObjectStreamPrinterTest
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test of the formatter is fairly coarse, formatting several
|
||||
* sample classes and spot checking the result string for key strings.
|
||||
*/
|
||||
@Test
|
||||
public class ObjectStreamPrinterTest {
|
||||
|
||||
// Override with (-DDEBUG=true) to see all the output
|
||||
private static boolean DEBUG = Boolean.getBoolean("DEBUG");
|
||||
|
||||
@DataProvider(name = "serializables")
|
||||
Object[][] serializables() {
|
||||
return new Object[][]{
|
||||
{new Object[]{"abc", "def"}, 0, 0, 2},
|
||||
{new Object[]{0, 1}, 2, 2, 0},
|
||||
{new Object[]{TimeUnit.DAYS, TimeUnit.SECONDS}, 2, 0, 2},
|
||||
{new Object[]{List.of("one", "two", "three")}, 1, 1, 3},
|
||||
{new Object[]{genList()}, 1, 1, 2},
|
||||
{new Object[]{genMap()}, 1, 1, 5},
|
||||
{new Object[]{genProxy()}, 5, 2, 9},
|
||||
{new Object[]{new char[]{'x', 'y', 'z'},
|
||||
new byte[]{0x61, 0x62, 0x63}, new int[]{4, 5, 6},
|
||||
new float[]{1.0f, 2.0f, 3.1415927f},
|
||||
new boolean[]{true, false, true},
|
||||
new Object[]{"first", 3, 3.14159f}}, 9, 2, 1},
|
||||
{ new Object[] {new XYPair(3, 5)}, 1, 1, 0},
|
||||
};
|
||||
}
|
||||
|
||||
@DataProvider(name = "SingleObjects")
|
||||
Object[][] sources() {
|
||||
return new Object[][]{
|
||||
{"A Simple", new A(), 1, 1, 0},
|
||||
{"BNoDefaultRO has no call to defaultReadObject", new BNoDefaultRO(), 2, 1, 1},
|
||||
{"BDefaultRO has call to defaultReadObject", new BDefaultRO(), 2, 1, 1},
|
||||
{"CNoDefaultRO extends BNoDefaultRO with no fields", new CNoDefaultRO(), 3, 1, 3},
|
||||
{"CDefaultRO extends BDefaultRO with no fields", new CDefaultRO(), 3, 1, 3},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks the output of serializing an object, using HexPrinter
|
||||
* with an ObjectStreamPrinter formatter, and spot checking the number of
|
||||
* class descriptors, objects, and strings.
|
||||
*
|
||||
* @param objs an array of objects
|
||||
* @param descriptors the expected count of class descriptors
|
||||
* @param objects the expected count of objects
|
||||
* @param strings the expected count of strings
|
||||
* @throws IOException if any I/O exception occurs
|
||||
*/
|
||||
@Test(dataProvider = "serializables")
|
||||
public void testFormat(Object[] objs, int descriptors, int objects, int strings) throws IOException {
|
||||
byte[] bytes = serializeObjects(objs);
|
||||
|
||||
String result = HexPrinter.simple()
|
||||
.formatter(ObjectStreamPrinter.formatter())
|
||||
.toString(bytes);
|
||||
if (DEBUG)
|
||||
System.out.println(result);
|
||||
expectStrings(result, "CLASSDESC #", descriptors);
|
||||
expectStrings(result, "OBJ #", objects);
|
||||
expectStrings(result, "STRING #", strings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the output of serializing an object, using an ObjectStreamPrinter formatter,
|
||||
* and spot checking the number of class descriptors, objects, and strings.
|
||||
*
|
||||
* @param objs an array of objects
|
||||
* @param descriptors the expected count of class descriptors
|
||||
* @param objects the expected count of objects
|
||||
* @param strings the expected count of strings
|
||||
* @throws IOException if any I/O exception occurs
|
||||
*/
|
||||
@Test(dataProvider = "serializables", enabled=true)
|
||||
static void standAlonePrinter(Object[] objs, int descriptors, int objects, int strings) throws IOException{
|
||||
byte[] bytes = serializeObjects(objs);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
try (InputStream in = new ByteArrayInputStream(bytes);
|
||||
DataInputStream din = new DataInputStream(in)) {
|
||||
var p = ObjectStreamPrinter.formatter();
|
||||
String s;
|
||||
while (!(s = p.annotate(din)).isEmpty()) {
|
||||
sb.append(s);
|
||||
}
|
||||
} catch (EOFException eof) {
|
||||
// Done
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
String result = sb.toString();
|
||||
if (DEBUG)
|
||||
System.out.println(result);
|
||||
expectStrings(result, "CLASSDESC #", descriptors);
|
||||
expectStrings(result, "OBJ #", objects);
|
||||
expectStrings(result, "STRING #", strings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the output of serializing an object, using HexPrinter
|
||||
* with an ObjectStreamPrinter formatter, and spot checking the number of
|
||||
* class descriptors, objects, and strings.
|
||||
*
|
||||
* @param label a label string for the object being serialized
|
||||
* @param o an object
|
||||
* @param descriptors the expected count of class descriptors
|
||||
* @param objects the expected count of objects
|
||||
* @param strings the expected count of strings
|
||||
* @throws IOException if any I/O exception occurs
|
||||
*/
|
||||
@Test(dataProvider = "SingleObjects")
|
||||
static void singleObjects(String label, Object o, int descriptors, int objects, int strings) throws IOException {
|
||||
if (DEBUG)
|
||||
System.out.println("Case: " + label);
|
||||
ByteArrayOutputStream boas = new ByteArrayOutputStream();
|
||||
try (ObjectOutputStream os = new ObjectOutputStream(boas)) {
|
||||
os.writeObject(o);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
byte[] bytes = boas.toByteArray();
|
||||
String result = HexPrinter.simple()
|
||||
.formatter(ObjectStreamPrinter.formatter(), "// ", 120)
|
||||
.toString(bytes);
|
||||
if (DEBUG)
|
||||
System.out.println(result);
|
||||
expectStrings(result, "CLASSDESC #", descriptors);
|
||||
expectStrings(result, "OBJ #", objects);
|
||||
expectStrings(result, "STRING #", strings);
|
||||
}
|
||||
|
||||
/**
|
||||
* A specific test case for (TC_LONGSTRING) of a stream
|
||||
* containing a very long (0x10000) character string.
|
||||
*
|
||||
* @throws IOException if any I/O exception occurs
|
||||
*/
|
||||
@Test
|
||||
static void longString() throws IOException {
|
||||
String large = " 123456789abcedf".repeat(0x1000);
|
||||
|
||||
ByteArrayOutputStream boas = new ByteArrayOutputStream();
|
||||
try (ObjectOutputStream os = new ObjectOutputStream(boas)) {
|
||||
os.writeObject(large);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
byte[] bytes = boas.toByteArray();
|
||||
String result = HexPrinter.simple()
|
||||
.formatter(ObjectStreamPrinter.formatter(), "// ", 16 * 8 - 1)
|
||||
.toString(bytes);
|
||||
long lineCount = result.lines().count();
|
||||
assertEquals(4610, lineCount, "too many/few lines in result");
|
||||
if (DEBUG || lineCount != 4610) {
|
||||
// Show first few lines
|
||||
int off = 0;
|
||||
for (int c = 0; c < 4; c++)
|
||||
off = result.indexOf('\n', off + 1);
|
||||
System.out.println(result.substring(0, off));
|
||||
System.out.println("...");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the main method (without launching a separate process)
|
||||
* passing a file name as a parameter.
|
||||
* Each file should be formatted to stdout with no exceptions
|
||||
* @throws IOException if an I/O exception occurs
|
||||
*/
|
||||
@Test
|
||||
static void testMain() throws IOException {
|
||||
Object[] objs = {genList()};
|
||||
byte[] bytes = serializeObjects(objs); // A serialized List
|
||||
Path p = Path.of("scratch.tmp");
|
||||
Files.write(p, bytes);
|
||||
String[] args = {p.toString()};
|
||||
ObjectStreamPrinter.main(args); // invoke main with the file name
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize multiple objects to a single stream and return a byte array.
|
||||
* @param obj an array of Objects to serialize
|
||||
* @return a byte array with the serilized objects.
|
||||
* @throws IOException
|
||||
*/
|
||||
private static byte[] serializeObjects(Object[] obj) throws IOException {
|
||||
byte[] bytes;
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
|
||||
for (Object o : obj)
|
||||
oos.writeObject(o);
|
||||
oos.flush();
|
||||
bytes = baos.toByteArray();
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the result string contains a number of key strings.
|
||||
* If not, it asserts an exception.
|
||||
* @param result the result string of formatting
|
||||
* @param key a key string to count
|
||||
* @param expectedCount the expected count of strings
|
||||
*/
|
||||
static void expectStrings(String result, String key, int expectedCount) {
|
||||
int count = 0;
|
||||
for (int i = result.indexOf(key); i >= 0; i = result.indexOf(key, i + 1)) {
|
||||
count++;
|
||||
}
|
||||
assertEquals(count, expectedCount, "Occurrences of " + key);
|
||||
}
|
||||
|
||||
public static List<String> genList() {
|
||||
List<String> l = new ArrayList<>();
|
||||
l.add("abc");
|
||||
l.add("def");
|
||||
return l;
|
||||
}
|
||||
|
||||
public static Map<String, String> genMap() {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("1", "One");
|
||||
map.put("2", "Two");
|
||||
map.put("2.2", "Two");
|
||||
return map;
|
||||
}
|
||||
|
||||
public static Object genProxy() {
|
||||
InvocationHandler h = (InvocationHandler & Serializable) (Object proxy, Method method, Object[] args) -> null;
|
||||
Class<?>[] intf = new Class<?>[]{Serializable.class, DataInput.class, DataOutput.class};
|
||||
return Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), intf, h);
|
||||
}
|
||||
|
||||
static class A implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
int aIntValue = 1;
|
||||
}
|
||||
static class BNoDefaultRO extends A {
|
||||
private static final long serialVersionUID = 2L;
|
||||
private void writeObject(ObjectOutputStream os) throws IOException {
|
||||
os.writeInt(32);
|
||||
os.writeObject("bbb");
|
||||
}
|
||||
private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {
|
||||
is.readInt();
|
||||
is.readObject();
|
||||
}
|
||||
}
|
||||
|
||||
static class BDefaultRO extends A {
|
||||
private static final long serialVersionUID = 3L;
|
||||
private long bLongValue = 65535L;
|
||||
private void writeObject(ObjectOutputStream os) throws IOException {
|
||||
os.defaultWriteObject();
|
||||
os.writeInt(32);
|
||||
os.writeObject("bbb");
|
||||
}
|
||||
private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {
|
||||
is.defaultReadObject();
|
||||
is.readInt();
|
||||
is.readObject();
|
||||
}
|
||||
}
|
||||
static class CNoDefaultRO extends BNoDefaultRO {
|
||||
private static final long serialVersionUID = 4L;
|
||||
int cIntValue = Integer.MIN_VALUE;
|
||||
String cString = "ccc";
|
||||
}
|
||||
static class CDefaultRO extends BDefaultRO {
|
||||
private static final long serialVersionUID = 5L;
|
||||
int cIntvalue = Integer.MIN_VALUE;
|
||||
String cString = "ccc";
|
||||
}
|
||||
|
||||
static class XYPair implements Serializable {
|
||||
private static final long serialVersionUID = 6L;
|
||||
private int x;
|
||||
private int y;
|
||||
XYPair(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
210
test/lib-test/jdk/test/lib/hexdump/StreamDumpTest.java
Normal file
210
test/lib-test/jdk/test/lib/hexdump/StreamDumpTest.java
Normal file
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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.hexdump;
|
||||
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Test StreamDump utility
|
||||
* @library /test/lib
|
||||
* @build jdk.test.lib.hexdump.StreamDump
|
||||
* @run testng jdk.test.lib.hexdump.StreamDumpTest
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test of the formatter is fairly coarse, formatting several
|
||||
* sample classes and spot checking the result string for key strings.
|
||||
*/
|
||||
@Test
|
||||
public class StreamDumpTest {
|
||||
|
||||
private final static Path workDir = Path.of(".");
|
||||
|
||||
private final static String classpath = System.getProperty("test.class.path", ".");
|
||||
|
||||
private final static String testJDK = System.getProperty("test.jdk");
|
||||
|
||||
private final static String testSRC = System.getProperty("test.src", ".");
|
||||
|
||||
private final static String serializedListPath = createTmpSer();
|
||||
|
||||
/**
|
||||
* Create a file containing an example serialized list.
|
||||
* @return the path to the file.
|
||||
*/
|
||||
private static String createTmpSer() {
|
||||
try {
|
||||
Object[] objs = {genList()};
|
||||
byte[] bytes = serializeObjects(objs); // A serialized List
|
||||
Path path = Files.createTempFile(workDir, "list", ".ser");
|
||||
Files.write(path, bytes);
|
||||
return path.toString();
|
||||
} catch (IOException ioe) {
|
||||
throw new UncheckedIOException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Arguments lists to be passed when invoking StreamDump.
|
||||
* Arg list and the expected exit status, stdout line count, and stderr line count.
|
||||
* @return array of argument list arrays.
|
||||
*/
|
||||
@DataProvider(name = "serializables")
|
||||
Object[][] serializables() {
|
||||
return new Object[][] {
|
||||
{new String[]{testSRC + "/openssl.p12.pem"},
|
||||
0, 126, 0},
|
||||
{new String[]{"--formatter", "jdk.test.lib.hexdump.ASN1Formatter", testSRC + "/openssl.p12.pem"},
|
||||
0, 126, 0},
|
||||
{new String[]{serializedListPath},
|
||||
0, 19, 0},
|
||||
{new String[]{"--formatter", "jdk.test.lib.hexdump.ObjectStreamPrinter", serializedListPath},
|
||||
0, 19, 0},
|
||||
{new String[]{},
|
||||
1, 2, 0}, // no file arguments
|
||||
{new String[]{"--formatter"},
|
||||
1, 2, 0}, // --formatter option requires a class name
|
||||
{new String[]{"-formatter", "jdk.test.lib.hexdump.ObjectStreamPrinter"},
|
||||
1, 2, 0}, // options start with double "--"
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test the main method (without launching a separate process)
|
||||
* passing a file name as a parameter.
|
||||
* Each file should be formatted to stdout with no exceptions
|
||||
* @throws IOException if an I/O exception occurs
|
||||
*/
|
||||
@Test(dataProvider="serializables")
|
||||
static void testStreamDump(String[] args, int expectedStatus, int expectedStdout, int expectedStderr) throws IOException {
|
||||
List<String> argList = new ArrayList<>();
|
||||
argList.add(testJDK + "/bin/" + "java");
|
||||
argList.add("-classpath");
|
||||
argList.add(classpath);
|
||||
argList.add("jdk.test.lib.hexdump.StreamDump");
|
||||
argList.addAll(Arrays.asList(args));
|
||||
|
||||
Path stdoutPath = Files.createTempFile(workDir, "stdout", ".log");
|
||||
Path stderrPath = Files.createTempFile(workDir, "stderr", ".log");
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(argList);
|
||||
pb.redirectOutput(stdoutPath.toFile());
|
||||
pb.redirectOutput(stdoutPath.toFile());
|
||||
|
||||
System.out.println("args: " + argList);
|
||||
Process p = pb.start();
|
||||
try {
|
||||
int actualStatus = p.waitFor();
|
||||
fileCheck(stdoutPath, expectedStdout);
|
||||
fileCheck(stderrPath, expectedStderr);
|
||||
assertEquals(actualStatus, expectedStatus, "Unexpected exit status");
|
||||
} catch (InterruptedException ie) {
|
||||
ie.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the file exists and contains the expected number of lines.
|
||||
* @param path a file path
|
||||
* @param expectedLines the number of lines expected
|
||||
* @throws IOException if an I/O exception occurs
|
||||
*/
|
||||
static void fileCheck(Path path, int expectedLines) throws IOException {
|
||||
long actualLines = Files.newBufferedReader(path).lines().count();
|
||||
if (actualLines != expectedLines) {
|
||||
System.out.printf("%s: lines %d, expected: %d%n", path, actualLines, expectedLines);
|
||||
System.out.println("---Begin---");
|
||||
Files.newBufferedReader(path).lines().forEach(s -> System.out.println(s));
|
||||
System.out.println("----End----");
|
||||
}
|
||||
assertEquals(actualLines, expectedLines, "Unexpected line count");
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize multiple objects to a single stream and return a byte array.
|
||||
*
|
||||
* @param obj an array of Objects to serialize
|
||||
* @return a byte array with the serilized objects.
|
||||
* @throws IOException if an I/O exception occurs
|
||||
*/
|
||||
private static byte[] serializeObjects(Object[] obj) throws IOException {
|
||||
byte[] bytes;
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
|
||||
for (Object o : obj)
|
||||
oos.writeObject(o);
|
||||
oos.flush();
|
||||
bytes = baos.toByteArray();
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static List<String> genList() {
|
||||
List<String> l = new ArrayList<>();
|
||||
l.add("abc");
|
||||
l.add("def");
|
||||
return l;
|
||||
}
|
||||
|
||||
public static Map<String, String> genMap() {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("1", "One");
|
||||
map.put("2", "Two");
|
||||
map.put("2.2", "Two");
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
908
test/lib/jdk/test/lib/hexdump/ObjectStreamPrinter.java
Normal file
908
test/lib/jdk/test/lib/hexdump/ObjectStreamPrinter.java
Normal file
@ -0,0 +1,908 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2021, 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.hexdump;
|
||||
|
||||
import java.io.CharArrayWriter;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectStreamConstants;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.UTFDataFormatException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static java.io.ObjectStreamConstants.STREAM_MAGIC;
|
||||
import static java.io.ObjectStreamConstants.TC_BLOCKDATA;
|
||||
import static java.io.ObjectStreamConstants.TC_BLOCKDATALONG;
|
||||
import static java.io.ObjectStreamConstants.TC_ENDBLOCKDATA;
|
||||
import static java.io.ObjectStreamConstants.TC_MAX;
|
||||
import static java.io.ObjectStreamConstants.TC_NULL;
|
||||
import static java.io.ObjectStreamConstants.baseWireHandle;
|
||||
|
||||
/**
|
||||
* Object Serialization stream annotation printer.
|
||||
* On the first call, the stream header is expected, raw bytes
|
||||
* are printed until the header is found.
|
||||
* Each call decodes a hierarchy rooted at the first typecode.
|
||||
* Unrecognized bytes are read and printed.
|
||||
*/
|
||||
public class ObjectStreamPrinter implements HexPrinter.Formatter {
|
||||
|
||||
private int nextHandle = 0;
|
||||
private List<Handle> labels = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Returns an ObjectStreamPrinter.
|
||||
* @return an ObjectStreamPrinter
|
||||
*/
|
||||
public static ObjectStreamPrinter formatter() {
|
||||
return new ObjectStreamPrinter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ObjectStreamPrinter.
|
||||
*/
|
||||
private ObjectStreamPrinter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Read bytes from the stream and annotate the stream as a
|
||||
* ObjectInputStream. Each call to convert() reads an object.
|
||||
* Before the first object is read, the Stream header is expected and annotated.
|
||||
* Unrecognized bytes are printed as decimal values.
|
||||
*
|
||||
* @param in a DataInputStream
|
||||
* @return a string representation of the ObjectInputStream
|
||||
*/
|
||||
public String annotate(DataInputStream in) throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try {
|
||||
this.annotate(in, sb);
|
||||
} catch (IOException e) {
|
||||
// ignore the exception so the accumulated output can be returned
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read bytes from the stream and annotate the stream as a
|
||||
* ObjectInputStream. Each call to convert() reads an object.
|
||||
* Before the first object is read, the Stream header is expected
|
||||
* and annotated.
|
||||
* Unrecognized bytes are printed as decimal values.
|
||||
*
|
||||
* @param in a DataInputStream
|
||||
* @param out an Appendable for the output
|
||||
*/
|
||||
@Override
|
||||
public void annotate(DataInputStream in, Appendable out) throws IOException {
|
||||
annotate(in, out, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read bytes from the stream and annotate the stream as a
|
||||
* ObjectInputStream. Each call to convert() reads an object.
|
||||
* The Stream header is expected and annotated.
|
||||
* Unrecognized bytes are printed as decimal values.
|
||||
*
|
||||
* @param in a DataInputStream
|
||||
* @param out an Appendable for the output
|
||||
* @param indent indentation level
|
||||
*/
|
||||
public void annotate(DataInputStream in, Appendable out, int indent) throws IOException {
|
||||
// Read and format a single object, if its the header format another
|
||||
if (formatObject(in, out, indent).equals(HEADER_HANDLE))
|
||||
formatObject(in, out, indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and format objects in the stream based on the type code.
|
||||
* Unrecognized type codes are printed in decimal.
|
||||
*
|
||||
* @param in input stream
|
||||
* @param infoOut output stream
|
||||
* @param indent indentation level
|
||||
* @return a label for the object just read
|
||||
* @throws IOException if an error occurs on the stream
|
||||
*/
|
||||
Handle formatObject(DataInputStream in, Appendable infoOut, int indent) throws IOException {
|
||||
int tc;
|
||||
if (((tc = in.read()) < TC_NULL || tc > TC_MAX) && tc != ((STREAM_MAGIC >>> 8) & 0xff)) {
|
||||
if (tc < 0)
|
||||
throw new EOFException();
|
||||
infoOut.append("raw: [ " + tc + " ");
|
||||
while ((tc = in.read()) >= 0 && (tc < TC_NULL || tc > TC_MAX)) {
|
||||
infoOut.append(tc + " ");
|
||||
}
|
||||
infoOut.append("] ");
|
||||
if (tc < 0)
|
||||
throw new EOFException();
|
||||
}
|
||||
switch (tc) {
|
||||
case TC_NULL:
|
||||
return formatTC_NULL(in, infoOut);
|
||||
case ObjectStreamConstants.TC_REFERENCE:
|
||||
return formatTC_REFERENCE(in, infoOut);
|
||||
case ObjectStreamConstants.TC_CLASSDESC:
|
||||
return formatTC_CLASSDESC(in, infoOut, indent);
|
||||
case ObjectStreamConstants.TC_OBJECT:
|
||||
return formatTC_OBJECT(in, infoOut, indent);
|
||||
case ObjectStreamConstants.TC_STRING:
|
||||
return formatTC_STRING(in, infoOut, indent);
|
||||
case ObjectStreamConstants.TC_ARRAY:
|
||||
return formatTC_ARRAY(in, infoOut, indent);
|
||||
case ObjectStreamConstants.TC_CLASS:
|
||||
return formatTC_CLASS(in, infoOut, indent);
|
||||
case ObjectStreamConstants.TC_BLOCKDATA:
|
||||
return formatTC_BLOCKDATA(in, infoOut);
|
||||
case ObjectStreamConstants.TC_ENDBLOCKDATA:
|
||||
return formatTC_ENDBLOCKDATA(in, infoOut);
|
||||
case ObjectStreamConstants.TC_RESET:
|
||||
return formatTC_RESET(in, infoOut);
|
||||
case ObjectStreamConstants.TC_BLOCKDATALONG:
|
||||
return formatTC_BLOCKDATALONG(in, infoOut);
|
||||
case ObjectStreamConstants.TC_EXCEPTION:
|
||||
return formatTC_EXCEPTION(in, infoOut);
|
||||
case ObjectStreamConstants.TC_LONGSTRING:
|
||||
return formatTC_LONGSTRING(in, infoOut);
|
||||
case ObjectStreamConstants.TC_PROXYCLASSDESC:
|
||||
return formatTC_PROXYCLASSDESC(in, infoOut, indent);
|
||||
case ObjectStreamConstants.TC_ENUM:
|
||||
return formatTC_ENUM(in, infoOut, indent);
|
||||
case (STREAM_MAGIC >>> 8) & 0xff:
|
||||
return formatSTREAM_MAGIC(in, infoOut, indent);
|
||||
default:
|
||||
infoOut.append("data: " + tc + ' ');
|
||||
return EMPTY_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and print a UTF string tagged as a string.
|
||||
*
|
||||
* @param in input stream
|
||||
* @param infoOut output stream
|
||||
* @param indent indentation level
|
||||
* @return a label for the object just read
|
||||
* @throws IOException if an error occurs on the stream
|
||||
*/
|
||||
private Handle formatTC_STRING(DataInputStream in, Appendable infoOut, int indent)
|
||||
throws IOException {
|
||||
String s = in.readUTF();
|
||||
Handle handle = new Handle(s);
|
||||
setLabel(nextHandle, handle);
|
||||
infoOut.append(String.format("STRING #%d \"%s\" ", nextHandle++, s));
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and print a long UTF string.
|
||||
*
|
||||
* @param in input stream
|
||||
* @param infoOut output stream
|
||||
* @return a label for the object just read
|
||||
* @throws IOException if an error occurs on the stream
|
||||
*/
|
||||
private Handle formatTC_LONGSTRING(DataInputStream in, Appendable infoOut) throws IOException {
|
||||
long utflen = in.readLong();
|
||||
String s = "longstring " + nextHandle + ",len: " + utflen;
|
||||
Handle handle = new Handle(s);
|
||||
setLabel(nextHandle, handle);
|
||||
infoOut.append(String.format("LONGSTRING #%d \"", nextHandle++));
|
||||
|
||||
long count = 0;
|
||||
while (count < utflen) {
|
||||
int c = (char)(in.readUnsignedByte() & 0xff);
|
||||
int char2, char3;
|
||||
switch (c >> 4) {
|
||||
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
|
||||
/* 0xxxxxxx*/
|
||||
count++;
|
||||
break;
|
||||
case 12: case 13:
|
||||
/* 110x xxxx 10xx xxxx*/
|
||||
count += 2;
|
||||
if (count > utflen)
|
||||
throw new UTFDataFormatException(
|
||||
"malformed input: partial character at end");
|
||||
char2 = in.readUnsignedByte() & 0xff;
|
||||
if ((char2 & 0xC0) != 0x80)
|
||||
throw new UTFDataFormatException(
|
||||
"malformed input around byte " + count);
|
||||
c = (char)(((c & 0x1F) << 6) | (char2 & 0x3F));
|
||||
break;
|
||||
case 14:
|
||||
/* 1110 xxxx 10xx xxxx 10xx xxxx */
|
||||
count += 3;
|
||||
if (count > utflen)
|
||||
throw new UTFDataFormatException(
|
||||
"malformed input: partial character at end");
|
||||
char2 = in.readUnsignedByte() & 0xff;
|
||||
char3 = in.readUnsignedByte() & 0xff;
|
||||
if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80))
|
||||
throw new UTFDataFormatException(
|
||||
"malformed input around byte " + (count-1));
|
||||
c = (char)(((c & 0x0F) << 12) |
|
||||
((char2 & 0x3F) << 6) |
|
||||
((char3 & 0x3F) << 0));
|
||||
break;
|
||||
default:
|
||||
/* 10xx xxxx, 1111 xxxx */
|
||||
throw new UTFDataFormatException(
|
||||
"malformed input around byte " + count);
|
||||
}
|
||||
infoOut.append((char)c);
|
||||
}
|
||||
infoOut.append("\" ");
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a null.
|
||||
*
|
||||
* @param in input stream
|
||||
* @param infoOut output stream
|
||||
* @return a label for the object just read
|
||||
* @throws IOException if an error occurs on the stream
|
||||
*/
|
||||
private Handle formatTC_NULL(DataInputStream in, Appendable infoOut) throws IOException {
|
||||
infoOut.append("NULL; ");
|
||||
return NULL_HANDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and format a reference to a previously read object.
|
||||
*
|
||||
* @param in input stream
|
||||
* @param infoOut output stream
|
||||
* @return a label for the object just read
|
||||
* @throws IOException if an error occurs on the stream
|
||||
*/
|
||||
private Handle formatTC_REFERENCE(DataInputStream in, Appendable infoOut) throws IOException {
|
||||
int offset = in.readInt();
|
||||
int h = offset - baseWireHandle;
|
||||
Handle handle = getHandle(h);
|
||||
infoOut.append("REF #" + h + ' ' + getLabel(h) + ' ');
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and format a class descriptor.
|
||||
*
|
||||
* @param in input stream
|
||||
* @param infoOut output stream
|
||||
* @return a label for the object just read
|
||||
* @throws IOException if an error occurs on the stream
|
||||
*/
|
||||
private Handle formatTC_CLASSDESC(DataInputStream in, Appendable infoOut, int indent) throws IOException {
|
||||
char[] buf = new char[1]; // buffer for 1 char type names
|
||||
String name = in.readUTF();
|
||||
ClassHandle handle = new ClassHandle(name);
|
||||
setLabel(nextHandle, handle);
|
||||
infoOut.append(String.format("CLASSDESC #%d %s", nextHandle++, name));
|
||||
newlineIndent(infoOut, indent + 2);
|
||||
long svid = in.readLong();
|
||||
infoOut.append(String.format("svid: %dL", svid));
|
||||
newlineIndent(infoOut, indent + 2);
|
||||
int flags = in.readUnsignedByte();
|
||||
handle.setFlags(flags);
|
||||
StringJoiner flagsJoiner = new StringJoiner(", ");
|
||||
if (handle.hasFlag(ObjectStreamConstants.SC_WRITE_METHOD)) flagsJoiner.add("WRITE_OBJECT");
|
||||
if (handle.hasFlag(ObjectStreamConstants.SC_SERIALIZABLE)) flagsJoiner.add("SERIALIZABLE");
|
||||
if (handle.hasFlag(ObjectStreamConstants.SC_EXTERNALIZABLE)) flagsJoiner.add("EXTERNALIZABLE");
|
||||
if (handle.hasFlag(ObjectStreamConstants.SC_BLOCK_DATA)) flagsJoiner.add("BLOCK_DATA");
|
||||
if (handle.hasFlag(ObjectStreamConstants.SC_ENUM)) flagsJoiner.add("ENUM");
|
||||
infoOut.append(String.format("flags: %s", flagsJoiner.toString()));
|
||||
|
||||
newlineIndent(infoOut, indent + 2);
|
||||
int numFields = in.readShort();
|
||||
{ // Append list of fields
|
||||
infoOut.append(String.format("%d field(s) {", numFields));
|
||||
CharArrayWriter caw = new CharArrayWriter();
|
||||
for (int i = 0; i < numFields; i++) {
|
||||
newlineIndent(infoOut, indent + 4);
|
||||
buf[0] = (char) in.readByte();
|
||||
String fname = in.readUTF();
|
||||
caw.write(buf[0]);
|
||||
caw.write(' ');
|
||||
caw.write(fname);
|
||||
caw.write(' ');
|
||||
String typeName;
|
||||
if ((buf[0] == 'L') || (buf[0] == '[')) {
|
||||
typeName = formatObject(in, caw, 0).toString();
|
||||
} else {
|
||||
typeName = new String(buf);
|
||||
caw.write(mapClassName(typeName).toString());
|
||||
}
|
||||
caw.write("; ");
|
||||
infoOut.append(caw.toString());
|
||||
caw.reset();
|
||||
handle.addField(fname, typeName); // Add field to handle
|
||||
}
|
||||
if (numFields > 0)
|
||||
newlineIndent(infoOut, indent + 2);
|
||||
infoOut.append("} ");
|
||||
}
|
||||
skipCustomData(in, infoOut, indent);
|
||||
newlineIndent(infoOut, indent + 2);
|
||||
infoOut.append("Super: ");
|
||||
Handle sup = formatObject(in, infoOut, indent);
|
||||
if (sup instanceof ClassHandle) {
|
||||
handle.superClass((ClassHandle)sup);
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
private Appendable newlineIndent(Appendable infoOut, int indent) throws IOException {
|
||||
return infoOut.append(System.lineSeparator()).append(" ".repeat(indent));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and format custom data until end of block data.
|
||||
*
|
||||
* @param in input stream
|
||||
* @param infoOut output stream
|
||||
* @throws IOException if an error occurs on the stream
|
||||
*/
|
||||
private void skipCustomData(DataInputStream in, Appendable infoOut, int indent) throws IOException {
|
||||
while (!formatObject(in, infoOut, indent).equals(END_HANDLE)) {
|
||||
// ignore anything but ENDBLOCK
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and format a proxy class descriptor.
|
||||
*
|
||||
* @param in input stream
|
||||
* @param infoOut output stream
|
||||
* @return a label for the object just read
|
||||
* @throws IOException if an error occurs on the stream
|
||||
*/
|
||||
private Handle formatTC_PROXYCLASSDESC(DataInputStream in, Appendable infoOut, int indent) throws IOException {
|
||||
int numIfaces = in.readInt();
|
||||
setLabel(nextHandle, CLASS_HANDLE);
|
||||
infoOut.append(String.format("PROXYCLASSDESC #%d %d ",
|
||||
nextHandle++, numIfaces));
|
||||
if (numIfaces > 0) {
|
||||
CharArrayWriter caw = new CharArrayWriter();
|
||||
PrintWriter pw = new PrintWriter(caw);
|
||||
pw.append("{");
|
||||
String delim = "";
|
||||
for (int i = 0; i < numIfaces; i++, delim = ", ") {
|
||||
caw.write(delim);
|
||||
caw.write(in.readUTF());
|
||||
}
|
||||
pw.write("} ");
|
||||
infoOut.append(caw.toString());
|
||||
}
|
||||
skipCustomData(in, infoOut, indent);
|
||||
newlineIndent(infoOut, indent + 2);
|
||||
infoOut.append("Super: ");
|
||||
formatObject(in, infoOut, indent);
|
||||
return CLASS_HANDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and format a class.
|
||||
*
|
||||
* @param in input stream
|
||||
* @param infoOut output stream
|
||||
* @param indent indentation level
|
||||
* @return a label for the object just read
|
||||
* @throws IOException if an error occurs on the stream
|
||||
*/
|
||||
private Handle formatTC_CLASS(DataInputStream in, Appendable infoOut, int indent) throws IOException {
|
||||
Handle label = new Handle("class#" + nextHandle);
|
||||
setLabel(nextHandle, label);
|
||||
infoOut.append(String.format("CLASS #%d; ", nextHandle++));
|
||||
formatObject(in, infoOut, indent);
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and format an instance.
|
||||
*
|
||||
* @param in input stream
|
||||
* @param infoOut output stream
|
||||
* @param indent indentation level
|
||||
* @return a label for the object just read
|
||||
* @throws IOException if an error occurs on the stream
|
||||
*/
|
||||
private Handle formatTC_OBJECT(DataInputStream in, Appendable infoOut, int indent) throws IOException {
|
||||
infoOut.append("READ ");
|
||||
Handle type = formatObject(in, infoOut, indent);
|
||||
setLabel(nextHandle, OBJ_HANDLE);
|
||||
newlineIndent(infoOut, indent);
|
||||
infoOut.append(String.format("OBJ #%d %s ", nextHandle++, type.label()));
|
||||
|
||||
if (type instanceof ClassHandle) {
|
||||
ClassHandle[] types = ((ClassHandle) type).allClasses();
|
||||
for (ClassHandle ch : types) {
|
||||
ch.forEachField((n, v) -> {
|
||||
try {
|
||||
newlineIndent(infoOut, indent + 2).append(n).append(":");
|
||||
var cl = mapClassName(v.substring(0, 1));
|
||||
if (cl != Object.class) {
|
||||
var f = HexPrinter.getFormatter(cl, "%s ");
|
||||
f.annotate(in, infoOut);
|
||||
} else {
|
||||
formatObject(in, infoOut, indent + 2);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
// Check for Custom data followed by ENDBLOCK, if there was a writeObject method
|
||||
if (ch.hasFlag(ObjectStreamConstants.SC_WRITE_METHOD)) {
|
||||
newlineIndent(infoOut, indent + 2);
|
||||
infoOut.append("CustomData: ");
|
||||
Handle skipped;
|
||||
do {
|
||||
newlineIndent(infoOut, indent + 4);
|
||||
skipped = formatObject(in, infoOut, indent + 4);
|
||||
} while (!skipped.equals(END_HANDLE));
|
||||
}
|
||||
}
|
||||
}
|
||||
return OBJ_HANDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and format an Enum.
|
||||
*
|
||||
* @param in input stream
|
||||
* @param infoOut output stream
|
||||
* @param indent indentation level
|
||||
* @return a label for the object just read
|
||||
* @throws IOException if an error occurs on the stream
|
||||
*/
|
||||
private Handle formatTC_ENUM(DataInputStream in, Appendable infoOut, int indent) throws IOException {
|
||||
setLabel(nextHandle, ENUM_HANDLE);
|
||||
infoOut.append(String.format("READ "));
|
||||
Handle enumType = formatObject(in, infoOut, indent);
|
||||
Handle h = formatObject(in, infoOut, indent + 2);
|
||||
setLabel(nextHandle, h);
|
||||
newlineIndent(infoOut, indent);
|
||||
infoOut.append(String.format("ENUM #%d %s.%s ", nextHandle++, enumType.label(), h.label()));
|
||||
|
||||
return ENUM_HANDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and format an array.
|
||||
*
|
||||
* @param in input stream
|
||||
* @param infoOut output stream
|
||||
* @param indent indentation level
|
||||
* @return a label for the object just read
|
||||
* @throws IOException if an error occurs on the stream
|
||||
*/
|
||||
private Handle formatTC_ARRAY(DataInputStream in, Appendable infoOut, int indent) throws IOException {
|
||||
infoOut.append("READ ");
|
||||
Handle type = formatObject(in, infoOut, indent);
|
||||
newlineIndent(infoOut, indent);
|
||||
setLabel(nextHandle, ARRAY_HANDLE);
|
||||
int nelements = in.readInt();
|
||||
infoOut.append(String.format("ARRAY #%d ", nextHandle++));
|
||||
|
||||
infoOut.append(String.format("%d", nelements));
|
||||
if (type.toString().charAt(0) == '[') {
|
||||
infoOut.append("[");
|
||||
formatArray(in, infoOut, nelements, type.toString().charAt(1), indent + 2);
|
||||
infoOut.append("] ");
|
||||
}
|
||||
return ARRAY_HANDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and format block data.
|
||||
*
|
||||
* @param in input stream
|
||||
* @param infoOut output stream
|
||||
* @return a label for the object just read
|
||||
* @throws IOException if an error occurs on the stream
|
||||
*/
|
||||
private Handle formatTC_BLOCKDATA(DataInputStream in, Appendable infoOut) throws IOException {
|
||||
int l = in.readUnsignedByte();
|
||||
StringBuilder sb = new StringBuilder(32 + l + 2);
|
||||
sb.append("BLOCKDATA " + l + "[ ");
|
||||
for (int i = 0; i < l; i++) {
|
||||
int v = in.readUnsignedByte();
|
||||
sb.append(toPrintable((char)v));
|
||||
}
|
||||
sb.append("]; ");
|
||||
infoOut.append(sb.toString());
|
||||
return BLOCK_HANDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and format long block data.
|
||||
*
|
||||
* @param in input stream
|
||||
* @param infoOut output stream
|
||||
* @return a label for the object just read
|
||||
* @throws IOException if an error occurs on the stream
|
||||
*/
|
||||
private Handle formatTC_BLOCKDATALONG(DataInputStream in, Appendable infoOut) throws IOException {
|
||||
int l = in.readInt();
|
||||
StringBuilder sb = new StringBuilder(32 + l + 2);
|
||||
sb.append("BLOCKDATALONG: " + l + " [ ");
|
||||
for (int i = 0; i < l; i++) {
|
||||
int v = in.readUnsignedByte();
|
||||
sb.append(String.format("%02x ", v));
|
||||
}
|
||||
sb.append("]; ");
|
||||
infoOut.append(sb.toString());
|
||||
return BLOCKLONG_HANDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and format end-of-block-data.
|
||||
*
|
||||
* @param in input stream
|
||||
* @param infoOut output stream
|
||||
* @return a label for the object just read
|
||||
* @throws IOException if an error occurs on the stream
|
||||
*/
|
||||
private Handle formatTC_ENDBLOCKDATA(DataInputStream in, Appendable infoOut) throws IOException {
|
||||
infoOut.append("ENDBLOCK; ");
|
||||
return END_HANDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and format a stream exception.
|
||||
*
|
||||
* @param in input stream
|
||||
* @param infoOut output stream
|
||||
* @return a label for the object just read
|
||||
* @throws IOException if an error occurs on the stream
|
||||
*/
|
||||
private Handle formatTC_EXCEPTION(DataInputStream in, Appendable infoOut) throws IOException {
|
||||
infoOut.append("EXCEPTION; ");
|
||||
return EXCEPTION_HANDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and format a stream reset.
|
||||
*
|
||||
* @param in input stream
|
||||
* @param infoOut output stream
|
||||
* @return a label for the object just read
|
||||
* @throws IOException if an error occurs on the stream
|
||||
*/
|
||||
private Handle formatTC_RESET(DataInputStream in, Appendable infoOut) throws IOException {
|
||||
nextHandle = 0;
|
||||
infoOut.append("RESET; ");
|
||||
return RESET_HANDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the stream header.
|
||||
* The first byte already has been read.
|
||||
* @param in a DataInputStream
|
||||
* @param out an Appendable for the output
|
||||
* @param indent indentation level
|
||||
* @return a Handle for the Header - unused.
|
||||
* @throws IOException if an error occurs on the stream
|
||||
*/
|
||||
private Handle formatSTREAM_MAGIC(DataInputStream in, Appendable out, int indent) throws IOException {
|
||||
// Scan for the Serialization protocol header
|
||||
// Format anything else as bytes
|
||||
int high = 0xAC, low;
|
||||
String prefix = " ".repeat(indent);
|
||||
if ((low = in.read()) != 0xED || high != 0xAC) {
|
||||
out.append(prefix).append("data: " + high + ", " + low)
|
||||
.append(System.lineSeparator());
|
||||
if (low < 0)
|
||||
throw new EOFException();
|
||||
throw new IOException("malformed stream header");
|
||||
}
|
||||
|
||||
int s1 = in.readUnsignedShort();
|
||||
out.append(" ".repeat(indent));
|
||||
out.append(String.format("ObjectStream Version: %d", s1));
|
||||
newlineIndent(out, indent); // Start a new line for the object
|
||||
return HEADER_HANDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the label for a handle.
|
||||
*
|
||||
* @param handle
|
||||
* @param label
|
||||
*/
|
||||
private void setLabel(int handle, String label) {
|
||||
setLabel(handle, new Handle(label));
|
||||
}
|
||||
private void setLabel(int handle, Handle label) {
|
||||
while (labels.size() <= handle)
|
||||
labels.add(label);
|
||||
labels.set(handle, label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the label for a handle.
|
||||
*
|
||||
* @param handle
|
||||
* @return
|
||||
*/
|
||||
private String getLabel(int handle) {
|
||||
return getHandle(handle).label();
|
||||
}
|
||||
|
||||
private Handle getHandle(int handle) {
|
||||
return (handle < 0 || handle >= labels.size()) ? INVALID_HANDLE : labels.get(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a raw class name to a Class based on the type.
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
private Class<?> mapClassName(String name) {
|
||||
switch (name.substring(0, 1)) {
|
||||
case "I":
|
||||
return int.class;
|
||||
case "J":
|
||||
return long.class;
|
||||
case "Z":
|
||||
return boolean.class;
|
||||
case "S":
|
||||
return short.class;
|
||||
case "C":
|
||||
return char.class;
|
||||
case "F":
|
||||
return float.class;
|
||||
case "D":
|
||||
return double.class;
|
||||
case "B":
|
||||
return byte.class;
|
||||
case "L":
|
||||
case "[":
|
||||
return Object.class;
|
||||
default:
|
||||
throw new RuntimeException("unknown class char: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and format an array.
|
||||
*
|
||||
* @param in input stream
|
||||
* @param infoOut output stream
|
||||
* @param count the number of elements
|
||||
* @param type the type code
|
||||
* @param indent indentation level
|
||||
* @return a label for the object just read
|
||||
* @throws IOException if an error occurs on the stream
|
||||
*/
|
||||
private void formatArray(DataInputStream in, Appendable infoOut, int count, char type, int indent)
|
||||
throws IOException {
|
||||
switch (type) {
|
||||
case 'I':
|
||||
while (count-- > 0) {
|
||||
int v = in.readInt();
|
||||
infoOut.append(Integer.toString(v));
|
||||
if (count > 0) infoOut.append(' ');
|
||||
}
|
||||
break;
|
||||
case 'J':
|
||||
while (count-- > 0) {
|
||||
long v = in.readLong();
|
||||
infoOut.append(Long.toString(v));
|
||||
if (count > 0) infoOut.append(' ');
|
||||
}
|
||||
break;
|
||||
case 'C':
|
||||
while (count-- > 0) {
|
||||
int v = in.readUnsignedShort();
|
||||
infoOut.append((char) v);
|
||||
if (count > 0) infoOut.append(' ');
|
||||
}
|
||||
break;
|
||||
case 'S':
|
||||
while (count-- > 0) {
|
||||
int v = in.readUnsignedShort();
|
||||
infoOut.append(Integer.toString(v));
|
||||
if (count > 0) infoOut.append(' ');
|
||||
}
|
||||
break;
|
||||
case 'F':
|
||||
while (count-- > 0) {
|
||||
float v = in.readFloat();
|
||||
infoOut.append(Float.toString(v));
|
||||
if (count > 0) infoOut.append(' ');
|
||||
}
|
||||
break;
|
||||
case 'D':
|
||||
while (count-- > 0) {
|
||||
double v = in.readDouble();
|
||||
infoOut.append(Double.toString(v));
|
||||
if (count > 0) infoOut.append(' ');
|
||||
}
|
||||
break;
|
||||
case 'Z':
|
||||
while (count-- > 0) {
|
||||
boolean v = in.readBoolean();
|
||||
infoOut.append(v ? "true" : "false");
|
||||
if (count > 0) infoOut.append(' ');
|
||||
}
|
||||
break;
|
||||
case 'L':
|
||||
while (count-- > 0) {
|
||||
formatObject(in, infoOut, indent + 2);
|
||||
if (count > 0) newlineIndent(infoOut, indent);
|
||||
}
|
||||
break;
|
||||
case 'B':
|
||||
default: // anything unknown as bytes
|
||||
while (count-- > 0) {
|
||||
int v = in.readUnsignedByte();
|
||||
infoOut.append(toPrintable((char) v));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private char toPrintable(char ch) {
|
||||
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
|
||||
(ch >= '0' && ch <= '9')) {
|
||||
return ch;
|
||||
} else {
|
||||
switch (ch) {
|
||||
case ' ': /* space */
|
||||
case '\'': /* apostrophe */
|
||||
case '(': /* left paren */
|
||||
case ')': /* right paren */
|
||||
case '+': /* plus */
|
||||
case ',': /* comma */
|
||||
case '-': /* hyphen */
|
||||
case '.': /* period */
|
||||
case '/': /* slash */
|
||||
case ':': /* colon */
|
||||
case '=': /* equals */
|
||||
case '?': /* question mark */
|
||||
return ch;
|
||||
default:
|
||||
return '.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class Handle {
|
||||
private final String label;
|
||||
|
||||
Handle(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
String label() {
|
||||
return label;
|
||||
}
|
||||
public String toString() {
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
static final Handle EMPTY_HANDLE = new Handle("");
|
||||
static final Handle INVALID_HANDLE = new Handle("invalid handle");
|
||||
static final Handle NULL_HANDLE = new Handle("null");
|
||||
static final Handle RESET_HANDLE = new Handle("reset");
|
||||
static final Handle EXCEPTION_HANDLE = new Handle("exception");
|
||||
static final Handle END_HANDLE = new Handle("end");
|
||||
static final Handle BLOCK_HANDLE = new Handle("block");
|
||||
static final Handle BLOCKLONG_HANDLE = new Handle("blocklong");
|
||||
static final Handle ARRAY_HANDLE = new Handle("array");
|
||||
static final Handle ENUM_HANDLE = new Handle("enum");
|
||||
static final Handle OBJ_HANDLE = new Handle("obj");
|
||||
static final Handle CLASS_HANDLE = new Handle("class");
|
||||
static final Handle HEADER_HANDLE = new Handle("header");
|
||||
|
||||
static class ClassHandle extends Handle {
|
||||
private final Map<String, String> fields;
|
||||
private int flags;
|
||||
private ClassHandle[] allClasses;
|
||||
|
||||
ClassHandle(String label) {
|
||||
super(label);
|
||||
this.fields = new LinkedHashMap<>();
|
||||
allClasses = new ClassHandle[] { this };
|
||||
}
|
||||
|
||||
void addField(String name, String type) {
|
||||
fields.put(name, type);
|
||||
}
|
||||
|
||||
void superClass(ClassHandle superClass) {
|
||||
ClassHandle[] types = superClass.allClasses();
|
||||
types = Arrays.copyOf(types, types.length + 1);
|
||||
types[types.length - 1] = this;
|
||||
this.allClasses = types;
|
||||
}
|
||||
|
||||
void setFlags(int flags) {
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
boolean hasFlag(int flagBits) {
|
||||
return (flags & flagBits) != 0;
|
||||
}
|
||||
|
||||
void forEachField(BiConsumer<String, String> doit) {
|
||||
fields.forEach(doit);
|
||||
}
|
||||
|
||||
ClassHandle[] allClasses() {
|
||||
return allClasses;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(super.toString()).append(": ");
|
||||
fields.forEach( (k,v) -> sb
|
||||
.append(k)
|
||||
.append('=')
|
||||
.append(v)
|
||||
.append(", ")
|
||||
.append((allClasses == null) ? "" : "; super: " + allClasses[allClasses.length - 1].label()));
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Simple utility to open and print contents of one or more files as a serialized object stream.
|
||||
* @param args file names
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 1) {
|
||||
System.out.println("Usage: <object stream files>");
|
||||
return;
|
||||
}
|
||||
ObjectStreamPrinter fmt = ObjectStreamPrinter.formatter();
|
||||
for (String file : args) {
|
||||
System.out.printf("%s%n", file);
|
||||
try (InputStream is = Files.newInputStream(Path.of(file))) {
|
||||
|
||||
DataInputStream dis = new DataInputStream(is);
|
||||
HexPrinter p = HexPrinter.simple()
|
||||
.dest(System.out)
|
||||
.formatter(ObjectStreamPrinter.formatter(), "; ", 100);
|
||||
p.format(dis);
|
||||
} catch (EOFException eof) {
|
||||
System.out.println();
|
||||
} catch (IOException ioe) {
|
||||
System.out.printf("%s: %s%n", file, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
219
test/lib/jdk/test/lib/hexdump/StreamDump.java
Normal file
219
test/lib/jdk/test/lib/hexdump/StreamDump.java
Normal file
@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2021, 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.hexdump;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
/**
|
||||
* A simple command line StreamDump utility for files.
|
||||
* Usage: [-formatter <class name>] <files>
|
||||
*
|
||||
* The fully qualified class name of a HexPrinter.Formatter can be supplied using `--formatter`.
|
||||
* If none is supplied, it attempts to guess the type (a ObjectStream)
|
||||
* or an ASN.1 encoded certificate; otherwise the default is HexPrinter.Formatters.PRINTABLE.
|
||||
*/
|
||||
public class StreamDump {
|
||||
static HexPrinter.Formatter defaultFormatter = null;
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
List<String> argList = parseOptions(args);
|
||||
dumpFiles(argList);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
System.out.println(iae.getMessage());
|
||||
usage();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse command line options and return any remaining (filename) arguments.
|
||||
* @param arg the array of string arguments to main
|
||||
* @return A
|
||||
* @throws IllegalArgumentException if any arguments cannot be parsed
|
||||
*/
|
||||
private static List<String> parseOptions(String[] arg) {
|
||||
for (int i = 0; i < arg.length; i++) {
|
||||
if (!arg[i].startsWith("--")) {
|
||||
if (arg[i].startsWith("-"))
|
||||
throw new IllegalArgumentException("options start with '--', not single '-'");
|
||||
return Arrays.asList(Arrays.copyOfRange(arg, i, arg.length));
|
||||
}
|
||||
if (arg[i].equals("--formatter")) {
|
||||
if (++i >= arg.length)
|
||||
throw new IllegalArgumentException("Formatter class name missing");
|
||||
String fmtName = arg[i];
|
||||
try {
|
||||
defaultFormatter = findFormatter(Class.forName(fmtName));
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalArgumentException("Formatter class not found: " + fmtName);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No file arguments");
|
||||
}
|
||||
|
||||
private static void usage() {
|
||||
System.out.println("Usage: [--formatter <class name>] <files>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the file preceded by the title.
|
||||
* If the formatter is null, the type of content is guessed to choose a formatter.
|
||||
*
|
||||
* @param args an array of file names
|
||||
*/
|
||||
static void dumpFiles(List<String> args) {
|
||||
var beforeFileSpacing = "";
|
||||
for (int i = 0; i < args.size(); i++) {
|
||||
var file = args.get(i);
|
||||
try {
|
||||
System.out.print(beforeFileSpacing);
|
||||
var title = (args.size() > 1) ? String.format("File: %s%n" , file) : "";
|
||||
dumpFile(Path.of(file), defaultFormatter, title);
|
||||
beforeFileSpacing = System.lineSeparator();
|
||||
} catch (FileNotFoundException | NoSuchFileException fnf) {
|
||||
System.out.printf("File: %s file not found%n", file);
|
||||
} catch (IOException ioe) {
|
||||
System.out.printf("File: %s %s: %s%n", file, ioe.getClass().getName(), ioe.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the file preceded by the title.
|
||||
* If the formatter is null, the type of content is guessed to choose a formatter.
|
||||
*
|
||||
* @param file a file name, not null
|
||||
* @param defaultFormatter a HexPrinter formatter, may be null
|
||||
* @param title a title, may be empty, not null
|
||||
* @throws IOException if an exception occurs
|
||||
*/
|
||||
static void dumpFile(Path file, HexPrinter.Formatter defaultFormatter, String title) throws IOException {
|
||||
Objects.requireNonNull(file, "filename");
|
||||
Objects.requireNonNull(title, "title");
|
||||
try (InputStream fis = Files.newInputStream(file)) {
|
||||
System.out.print(title);
|
||||
dumpFile(fis, defaultFormatter);
|
||||
}
|
||||
}
|
||||
|
||||
static void dumpFile(InputStream fis, HexPrinter.Formatter formatter) throws IOException {
|
||||
try (BufferedInputStream is = new BufferedInputStream(fis)) {
|
||||
is.mark(1024);
|
||||
|
||||
InputStream decoded = decodeMaybe(is);
|
||||
if (!decoded.equals(is)) {
|
||||
if (formatter == null)
|
||||
formatter = findFormatter(ASN1Formatter.class); // Assume encoded ASN.1
|
||||
decoded = new BufferedInputStream(decoded);
|
||||
decoded.mark(1024);
|
||||
} else {
|
||||
decoded.reset();
|
||||
}
|
||||
if (formatter == null && guessSerializable(decoded)) {
|
||||
// Select formatter for a serializable stream
|
||||
formatter = findFormatter(ObjectStreamPrinter.class);
|
||||
}
|
||||
decoded.reset();
|
||||
|
||||
if (formatter == null)
|
||||
formatter = HexPrinter.Formatters.PRINTABLE;
|
||||
HexPrinter.simple()
|
||||
.formatter(formatter)
|
||||
.dest(System.out)
|
||||
.format(decoded);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the stream looks like Base64 Mime, return a stream to decode it.
|
||||
* @param is InputStream
|
||||
* @return an InputStream, unchanged unless it is Base64 Mime
|
||||
* @throws IOException if an I/O Error occurs
|
||||
*/
|
||||
static InputStream decodeMaybe(InputStream is) throws IOException {
|
||||
DataInputStream dis = new DataInputStream(is);
|
||||
is.mark(1024);
|
||||
String line1 = dis.readLine();
|
||||
if (line1.startsWith("-----")) {
|
||||
return Base64.getMimeDecoder().wrap(is);
|
||||
}
|
||||
is.reset();
|
||||
return is;
|
||||
}
|
||||
|
||||
static boolean guessSerializable(InputStream is) throws IOException {
|
||||
byte[] bytes = new byte[4];
|
||||
int l = is.read(bytes, 0, bytes.length);
|
||||
if (l >= 4 && (bytes[0] & 0xff) == 0xAC && (bytes[1] & 0xff) == 0xED &&
|
||||
bytes[2] == 0x00 && bytes[3] == 0x05) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static HexPrinter.Formatter findFormatter(Class<?> clazz) {
|
||||
Method[] methods = clazz.getDeclaredMethods();
|
||||
for (Method m : methods) {
|
||||
if (m.getReturnType() == clazz &&
|
||||
m.getParameterCount() == 0 &&
|
||||
Modifier.isStatic(m.getModifiers()) &&
|
||||
Modifier.isPublic(m.getModifiers())) {
|
||||
try {
|
||||
return (HexPrinter.Formatter)m.invoke(null);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Constructor<?>[] cons = clazz.getDeclaredConstructors();
|
||||
for (Constructor<?> m : cons) {
|
||||
if (m.getParameterCount() == 0 &&
|
||||
Modifier.isPublic(m.getModifiers())) {
|
||||
try {
|
||||
return (HexPrinter.Formatter)m.newInstance();
|
||||
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
|
||||
e.printStackTrace();
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("No formatter for class " + clazz.getName());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user