package com.fasterxml.jackson.databind.interop;
import java.io.*;
import com.fasterxml.jackson.databind.*;
/**
* Simple test to ensure that we can make POJOs use Jackson
* for JDK serialization, via {@link Externalizable}
*
* @since 2.1
*/
public class TestExternalizable extends BaseMapTest
{
/* Not pretty, but needed to make ObjectMapper accessible from
* static context (alternatively could use ThreadLocal).
*/
static class MapperHolder {
private final ObjectMapper mapper = new ObjectMapper();
private final static MapperHolder instance = new MapperHolder();
public static ObjectMapper mapper() { return instance.mapper; }
}
/**
* Helper class we need to adapt {@link ObjectOutput} as
* {@link OutputStream}
*/
final static class ExternalizableInput extends InputStream
{
private final ObjectInput in;
public ExternalizableInput(ObjectInput in) {
this.in = in;
}
@Override
public int available() throws IOException {
return in.available();
}
@Override
public void close() throws IOException {
in.close();
}
@Override
public boolean markSupported() {
return false;
}
@Override
public int read() throws IOException {
return in.read();
}
@Override
public int read(byte[] buffer) throws IOException {
return in.read(buffer);
}
@Override
public int read(byte[] buffer, int offset, int len) throws IOException {
return in.read(buffer, offset, len);
}
@Override
public long skip(long n) throws IOException {
return in.skip(n);
}
}
/**
* Helper class we need to adapt {@link ObjectOutput} as
* {@link OutputStream}
*/
final static class ExternalizableOutput extends OutputStream
{
private final ObjectOutput out;
public ExternalizableOutput(ObjectOutput out) {
this.out = out;
}
@Override
public void flush() throws IOException {
out.flush();
}
@Override
public void close() throws IOException {
out.close();
}
@Override
public void write(int ch) throws IOException {
out.write(ch);
}
@Override
public void write(byte[] data) throws IOException {
out.write(data);
}
@Override
public void write(byte[] data, int offset, int len) throws IOException {
out.write(data, offset, len);
}
}
// @com.fasterxml.jackson.annotation.JsonFormat(shape=com.fasterxml.jackson.annotation.JsonFormat.Shape.ARRAY)
@SuppressWarnings("resource")
static class MyPojo implements Externalizable
{
public int id;
public String name;
public int[] values;
public MyPojo() { } // for deserialization
public MyPojo(int id, String name, int[] values)
{
this.id = id;
this.name = name;
this.values = values;
}
@Override
public void readExternal(ObjectInput in) throws IOException
{
// MapperHolder.mapper().readValue(
MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
}
@Override
public void writeExternal(ObjectOutput oo) throws IOException
{
MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
}
@Override
public boolean equals(Object o)
{
if (o == this) return true;
if (o == null) return false;
if (o.getClass() != getClass()) return false;
MyPojo other = (MyPojo) o;
if (other.id != id) return false;
if (!other.name.equals(name)) return false;
if (other.values.length != values.length) return false;
for (int i = 0, end = values.length; i < end; ++i) {
if (values[i] != other.values[i]) return false;
}
return true;
}
}
/*
/**********************************************************
/* Actual tests
/**********************************************************
*/
// Comparison, using JDK native
static class MyPojoNative implements Serializable
{
private static final long serialVersionUID = 1L;
public int id;
public String name;
public int[] values;
public MyPojoNative(int id, String name, int[] values)
{
this.id = id;
this.name = name;
this.values = values;
}
}
@SuppressWarnings("unused")
public void testSerializeAsExternalizable() throws Exception
{
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
ObjectOutputStream obs = new ObjectOutputStream(bytes);
final MyPojo input = new MyPojo(13, "Foobar", new int[] { 1, 2, 3 } );
obs.writeObject(input);
obs.close();
byte[] ser = bytes.toByteArray();
// Ok: just verify it contains stuff it should
byte[] json = MapperHolder.mapper().writeValueAsBytes(input);
int ix = indexOf(ser, json);
if (ix < 0) {
fail("Serialization ("+ser.length+") does NOT contain JSON (of "+json.length+")");
}
// Sanity check:
if (false) {
bytes = new ByteArrayOutputStream();
obs = new ObjectOutputStream(bytes);
MyPojoNative p = new MyPojoNative(13, "Foobar", new int[] { 1, 2, 3 } );
obs.writeObject(p);
obs.close();
System.out.println("Native size: "+bytes.size()+", vs JSON: "+ser.length);
}
// then read back!
ObjectInputStream ins = new ObjectInputStream(new ByteArrayInputStream(ser));
MyPojo output = (MyPojo) ins.readObject();
ins.close();
assertNotNull(output);
assertEquals(input, output);
}
/*
/**********************************************************
/* Helper methods
/**********************************************************
*/
private int indexOf(byte[] full, byte[] fragment)
{
final byte first = fragment[0];
for (int i = 0, end = full.length-fragment.length; i < end; ++i) {
if (full[i] != first) continue;
if (matches(full, i, fragment)) {
return i;
}
}
return -1;
}
private boolean matches(byte[] full, int index, byte[] fragment)
{
for (int i = 1, end = fragment.length; i < end; ++i) {
if (fragment[i] != full[index+i]) {
return false;
}
}
return true;
}
}