How to disable the redundancy elimination feature of XMLEncoder to ensure POJOs completely serialised?

381 Views Asked by At

I have been using XMLEncoder to transform POJOs into XML and back again. Apart from storing POJOs, some XML output will also be used by other applications to generate other forms of data, such as reports.

So far this has worked well. Even as the POJOs evolve there have been no problems.

Recently I realised that not all values are actually output. Properties whose default values have not been altered will not be written. This is a problem for me.

From the Javadoc:

"The XMLEncoder class uses a redundancy elimination algorithm internally so that the default values of a Bean's properties are not written to the stream"

For me, it is important that the bean is output in its' entirety - including all default values.

Is there any way to disable this feature of XMLEncoder?

2

There are 2 best solutions below

0
On

I gave up on XMLEncoder and switched to XStream (see http://x-stream.github.io/).

Works well so far...

0
On

By default, the XMLEncoder does not serialize the properties of a Java Bean when they still have their default values. I also found this odd. Particularly, I wondered why there is no method like

xmlEncoder.setRedundancyEliminationEnabled(false);

However, there is one option to solve this without a third-party-library: In order to serialize a Java Bean completely, even if its properties still have the default values, one can use an own PersistenceDelegate.

Here is an example implementation of such a PersistenceDelegate:

import java.beans.BeanInfo;
import java.beans.Encoder;
import java.beans.Expression;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PersistenceDelegate;
import java.beans.PropertyDescriptor;
import java.beans.Statement;
import java.lang.reflect.Method;

/**
 * Implementation of a PersistenceDelegate that serializes all properties
 * of a Java Bean, even if they still have their default values.
 */
class FullPersistenceDelegate extends PersistenceDelegate
{
    @Override
    protected Expression instantiate(Object oldInstance, Encoder out)
    {
        return new Expression(
            oldInstance, oldInstance.getClass(), "new", null);
    }

    @Override
    protected void initialize(Class<?> type, Object oldInstance,
        Object newInstance, Encoder out)
    {
        super.initialize(type, oldInstance, newInstance, out);
        if (oldInstance.getClass() == type)
        {
            initializeProperties(type, oldInstance, out);
        }
    }

    /**
     * Write all statements to initialize the properties of the given 
     * Java Bean Type, based on the given instance, using the given
     * encoder
     *   
     * @param type The Java Bean Type
     * @param oldInstance The base instance
     * @param out The encoder
     */
    private void initializeProperties(
        Class<?> type, Object oldInstance, Encoder out)
    {
        BeanInfo info = null;
        try
        {
            info = Introspector.getBeanInfo(type);
        }
        catch (IntrospectionException ie)
        {
            out.getExceptionListener().exceptionThrown(ie);
            return;
        }
        PropertyDescriptor[] pds = info.getPropertyDescriptors();
        for (int i = 0; i < pds.length; ++i)
        {
            try
            {
                initializeProperty(type, pds[i], oldInstance, out);
            }
            catch (Exception e)
            {
                out.getExceptionListener().exceptionThrown(e);
            }
        }
    }

    /**
     * Write the statement to initialize the specified property of the given 
     * Java Bean Type, based on the given instance, using the given
     * encoder
     *   
     * @param type The Java Bean Type
     * @param pd The property descriptor
     * @param oldInstance The base instance
     * @param out The encoder
     * @throws Exception If the value can not be obtained
     */
    private void initializeProperty(
        Class<?> type, PropertyDescriptor pd, Object oldInstance, Encoder out) 
        throws Exception
    {
        Method getter = pd.getReadMethod();
        Method setter = pd.getWriteMethod();
        if (getter != null && setter != null)
        {
            Expression oldGetExpression =
                new Expression(oldInstance, getter.getName(), new Object[] {});
            Object oldValue = oldGetExpression.getValue();
            Statement setStatement =
                new Statement(oldInstance, setter.getName(), 
                    new Object[] { oldValue });
            out.writeStatement(setStatement);
        }
    }
}

Using this is straighforward: It just has to be registered for the encoder:

encoder.setPersistenceDelegate(ExampleBean.class,
    new FullPersistenceDelegate());

And that's it.

Here is a usage example. Given a simple example bean class...

public class ExampleBean
{
    enum ExampleBeanEnum
    {
        DEFAULT_ENUM_VALUE,
        MODIFIED_ENUM_VALUE,
    }
    private String stringValue;
    private int intValue;
    private ExampleBeanEnum enumValue;

    public ExampleBean()
    {
        stringValue = "Default String Value";
        intValue = 123;
        enumValue = ExampleBeanEnum.DEFAULT_ENUM_VALUE;
    }

    public String getStringValue()
    {
        return stringValue;
    }
    public void setStringValue(String stringValue)
    {
        this.stringValue = stringValue;
    }
    public int getIntValue()
    {
        return intValue;
    }
    public void setIntValue(int intValue)
    {
        this.intValue = intValue;
    }
    public ExampleBeanEnum getEnumValue()
    {
        return enumValue;
    }
    public void setEnumValue(ExampleBeanEnum enumValue)
    {
        this.enumValue = enumValue;
    }
}

and a test class...

import java.beans.ExceptionListener;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

public class FullPersistenceDelegateExample
{
    public static void main(String[] args)
    {
        // Create an XMLEncoder that writes to a byte array
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        XMLEncoder encoder = new XMLEncoder(stream);
        encoder.setExceptionListener(new ExceptionListener()
        {
            @Override
            public void exceptionThrown(Exception e)
            {
                e.printStackTrace();
            }
        });

        // Set the FullPersistenceDelegate for the ExampleBean class
        encoder.setPersistenceDelegate(ExampleBean.class,
            new FullPersistenceDelegate());

        // Write an instance of the ExampleBean, where only one property
        // was modified, as compared to the default value.
        // The default persistence delegate would cause only the modified
        // property to be written. However, the FullPersistenceDelegate
        // will cause ALL properties to be written
        ExampleBean oldExampleBean = new ExampleBean();
        oldExampleBean.setIntValue(234);

        encoder.writeObject(oldExampleBean);
        encoder.flush();
        encoder.close();

        // Print the encoding result
        System.out.println(stream);

        // Read the instance back and print its properties
        XMLDecoder d =
            new XMLDecoder(new ByteArrayInputStream(stream.toByteArray()));
        ExampleBean newExampleBean = (ExampleBean) d.readObject();
        System.out.println("stringValue: " + newExampleBean.getStringValue());
        System.out.println("intValue   : " + newExampleBean.getIntValue());
        System.out.println("enumValue  : " + newExampleBean.getEnumValue());
    }
}

one can see that the output contains the property values of all fields, even when they still have their default value:

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.7.0_65" class="java.beans.XMLDecoder">
 <object class="ExampleBean">
  <void property="enumValue">
   <object class="java.lang.Enum" method="valueOf">
    <class>ExampleBean$ExampleBeanEnum</class>
    <string>DEFAULT_ENUM_VALUE</string>
   </object>
  </void>
  <void property="intValue">
   <int>234</int>
  </void>
  <void property="stringValue">
   <string>Default String Value</string>
  </void>
 </object>
</java>

(Note that this has not been tested for more complex scenarios, but it works well for simple beans and may serve as a basis for own implementations).