Skip to main content

How to encode Type-Safe Enums?

Posted by malenkov on August 9, 2006 at 10:00 PM PDT

This is a sequel of my first article about Enums encoding.

As you may know, Type-Safe Enums were proposed by Joshua Bloch in Effective Java,

but they are not supported by XMLEncoder.
This article describes how to encode them into XML properly.

Let's create a simple Type-Safe Enum with static method to test encoding into XML:

import java.beans.XMLEncoder;
import java.io.ByteArrayOutputStream;

public final class TestEnum {
    public static final TestEnum LEFT = new TestEnum( "move to left" );
    public static final TestEnum RIGHT = new TestEnum( "move to right" );

    private final String info;

    private TestEnum( String name ) {
        this.info = name;
    }

    public String getInfo() {
        return this.info;
    }

    public static void main( String[] args ) {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();

        XMLEncoder encoder = new XMLEncoder( stream );
        encoder.writeObject( TestEnum.LEFT );
        encoder.writeObject( TestEnum.RIGHT );
        encoder.close();

        System.out.println( stream );
    }
}

If the test method is executed it will print the following result:

java.lang.InstantiationException: TestEnum
Continuing ...
java.lang.Exception: XMLEncoder: discarding statement XMLEncoder.writeObject(TestEnum);
Continuing ...
java.lang.InstantiationException: TestEnum
Continuing ...
java.lang.Exception: XMLEncoder: discarding statement XMLEncoder.writeObject(TestEnum);
Continuing ...
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.5.0_07" class="java.beans.XMLDecoder">
</java>

As you can see, some exceptions were thrown during encoding and XML does not contain any object.

Let's change the test program.
We should create persistence delegate and register it before encoding:

import java.beans.Encoder;
import java.beans.Expression;
import java.beans.PersistenceDelegate;
import java.beans.XMLEncoder;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public final class TestEnum {
    public static final TestEnum LEFT = new TestEnum( "move to left" );
    public static final TestEnum RIGHT = new TestEnum( "move to right" );

    private final String info;

    private TestEnum( String name ) {
        this.info = name;
    }

    public String getInfo() {
        return this.info;
    }

    public static void main( String[] args ) {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();

        XMLEncoder encoder = new XMLEncoder( stream );
        encoder.setPersistenceDelegate( TestEnum.class, new TypeSafeEnumPersistenceDelegate() );
        encoder.writeObject( TestEnum.LEFT );
        encoder.writeObject( TestEnum.RIGHT );
        encoder.close();

        System.out.println( stream );
    }
}

class TypeSafeEnumPersistenceDelegate extends PersistenceDelegate {
    protected boolean mutatesTo( Object oldInstance, Object newInstance ) {
        return oldInstance == newInstance;
    }

    protected Expression instantiate( Object oldInstance, Encoder out ) {
        Class type = oldInstance.getClass();
        if ( !Modifier.isPublic( type.getModifiers() ) )
            throw new IllegalArgumentException( "Could not instantiate instance of non-public class: " + oldInstance );

        for ( Field field : type.getFields() ) {
            int mod = field.getModifiers();
            if ( Modifier.isPublic( mod ) && Modifier.isStatic( mod ) && Modifier.isFinal( mod ) && ( type == field.getDeclaringClass() ) ) {
                try {
                    if ( oldInstance == field.get( null ) )
                        return new Expression( oldInstance, field, "get", new Object[]{null} );
                } catch ( IllegalAccessException exception ) {
                    throw new IllegalArgumentException( "Could not get value of the field: " + field, exception );
                }
            }
        }
        throw new IllegalArgumentException( "Could not instantiate value: " + oldInstance );
    }
}

Note that TypeSafeEnumPersistenceDelegate does not depend on concrete class.

So, it is possible to use one instance of such persistence delegate with any class,

that contains public static final fields with the same type,

but it is necessary to register it with this concrete class.

As a result, XML contains both encoded objects:

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.5.0_07" class="java.beans.XMLDecoder">
<object class="TestEnum" field="LEFT"/>
<object class="TestEnum" field="RIGHT"/>

</java>

I have bad news for you: JDK 6 does not support Type-Safe Enum encoding.

It is not so easy to integrate this code into DefaultPersistenceDelegate,

but I think I can do it for JDK 7 (it can affect other classes with public static final fields).

So, you should create custom persistence delegate and register it for your Type-Safe Enum.

Related Topics >>