Friday, January 13, 2006

Unit Testing Serialization Evolution, Take 2

Tom Hawtin suggested that I override writeClassDescriptor() instead. It's a good thing because in doing so I realized overridding writeUTF() as I did in my first try doesn't intercept field types correctly. Enjoy.
  public static <S> S serializeAndDeserialize(Object o,
      Class<S> spoofedType) throws IOException {
    ByteArrayOutputStream bout =
        new ByteArrayOutputStream();
    ObjectOutputStream oout =
        new SpoofingObjectOutputStream(bout, o.getClass(),
            spoofedType);
    oout.writeObject(o);
    oout.flush();
    oout.close();
    ByteArrayInputStream bin =
        new ByteArrayInputStream(bout.toByteArray());
    ObjectInputStream oin = new ObjectInputStream(bin);
    try {
      return spoofedType.cast(oin.readObject());
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
  }

  static class SpoofingObjectOutputStream
      extends ObjectOutputStream {

    String oldName;
    String newName;

    public SpoofingObjectOutputStream(OutputStream out,
        Class oldClass, Class newClass) throws IOException {
      super(out);
      this.oldName = oldClass.getName();
      this.newName = newClass.getName();
    }

    @Override protected void writeClassDescriptor(
        ObjectStreamClass descriptor) throws IOException {
      Class clazz = descriptor.forClass();

      boolean externalizable =
          Externalizable.class.isAssignableFrom(clazz);
      boolean serializable =
          Serializable.class.isAssignableFrom(clazz);
      boolean hasWriteObjectData =
          hasWriteObjectMethod(clazz);
      boolean isEnum = Enum.class.isAssignableFrom(clazz);

      writeUTF(replace(descriptor.getName()));
      writeLong(descriptor.getSerialVersionUID());
      byte flags = 0;
      if (externalizable) {
        flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
        flags |= ObjectStreamConstants.SC_BLOCK_DATA;
      } else if (serializable) {
        flags |= ObjectStreamConstants.SC_SERIALIZABLE;
      }
      if (hasWriteObjectData) {
        flags |= ObjectStreamConstants.SC_WRITE_METHOD;
      }
      if (isEnum) {
        flags |= ObjectStreamConstants.SC_ENUM;
      }
      writeByte(flags);

      ObjectStreamField[] fields = descriptor.getFields();
      writeShort(fields.length);
      for (ObjectStreamField field : fields) {
        writeByte(field.getTypeCode());
        writeUTF(field.getName());
        if (!field.isPrimitive()) {
          writeObject(replace(field.getTypeString()));
        }
      }
    }

    String replace(String className) {
      if (className.equals(newName)) {
        throw new RuntimeException(
            "Found instance of " + className + "."
                + " Expected instance of " + oldName + ".");
      }
      return className.equals(oldName) ? newName : className;
    }

    boolean hasWriteObjectMethod(Class clazz) {
      try {
        Method method =
            clazz.getDeclaredMethod("writeObject",
                ObjectOutputStream.class);
        int modifiers = method.getModifiers();
        return method.getReturnType() == Void.TYPE
            && !Modifier.isStatic(modifiers)
            && Modifier.isPrivate(modifiers);
      } catch (NoSuchMethodException e) {
        return false;
      }
    }
  }

3 Comments:

Blogger OhPunk! said...

What's with the Java? Fun fun! (I'm really anti-java these days)

Shaun
ohpunk.blogspot.com

3:57 PM  
Blogger Bob said...

Sorry. Blogger doesn't support categories or tags. :(

Maybe that's something else FeedBurner could add--tagging.

4:10 PM  
Blogger dcmoeller said...

Hi,

I've used your serialization testing toolkit for a while and it helps me a lot - thank you very much!

But for some weeks I'm going to apply Serialization Proxy pattern (as described in Josh Bloch's Effective Java book). And now your solution breaks.

After fiddling a little bit with the code I think I get a workable solution. Is there any way to post it alongside your solution?

Christian

4:59 AM  

Post a Comment

<< Home