Making XMLEncoder do something sensible with binary data

By default, XMLEncoder encodes byte arrays like any other array:

   <array class="byte" length="8">
    <void index="0">
     <byte>115</byte>
    </void>
    <void index="1">
     <byte>111</byte>
    </void>
    ...
   </array>

Repeat for every byte. This is bad.

A reasonable solution would be to store the whole byte array as a base 64 encoded string. Here’s how (it took a bit of figuring out):


public class ByteArrayPersistenceDelegate extends PersistenceDelegate {
    protected Expression instantiate(Object oldInstance, Encoder out) {
        byte[] e = (byte[]) oldInstance;
        return new Expression(e, ByteArrayPersistenceDelegate.class, "decode",
                new Object[]{ByteArrayPersistenceDelegate.encode(e)});
    }

// We want methods from this class to appear in the data - the indirection means we're
    // not tied to a particular Base64 implementation and are protected from interface changes.

public static byte[] decode(String encoded) {
        return org.apache.commons.codec.binary.Base64.decodeBase64(encoded);
    }

public static String encode(byte[] data) {
        return org.apache.commons.codec.binary.Base64.encodeBase64String(data);
    }
}

...

XMLEncoder e = new XMLEncoder(out) {
    public PersistenceDelegate getPersistenceDelegate(Class<?> type) {
        if (type == byte[].class)
            return del;
        else
            return super.getPersistenceDelegate(type);
    }
};
e.writeObject(someByteArrayOrSomeObjectContainingOne);
e.close();

See how you actually have to override getPersistenceDelegate()? You might think you’d setPersistenceDelegate() for byte[], but that doesn’t work – you still end up with the default array delegate and the default output.

That Base64 class is from the Commons Codec library, by the way.

Posted in Uncategorized | 1 Comment