Custom hashCode and damaged collections after load

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Custom hashCode and damaged collections after load

Алексеев Сергей

   Hello team. I got some question/problem with XStream.

   I have a class with custom realization of methods equals/hashCode. Such objects contains in HashSet collection. Default XStream behavior: create object instance, read attributes, read inner fields, return object and add it to collection if needed.
   The problem: due a specific object graph on some step of deserialization object was created, added to collection, and after all other fields was loaded. After fields was loaded, result of hashCode was changed, causing "damage" to collection (because HashSet uses HashMap inside). That happens because xstream got object with null fields from cache by 'refference' attribute. This does not looks like a bug, but working behavior is not ideal.
In my project I fixed it by restoring collection (just recreate it with same objects) in readResolve of other object (1 level upward in graph).
   There are other problem: calling readResolve does not guarantee that all fields of 'this' object (including sub objects in fields) was fully deserialized at moment of call. That why I cant fix collection  in readResolve of object with HashSet or HashMap and should do it in other place.

   There are attach file that nicely describes a situation.



---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email

Main.java (6K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Custom hashCode and damaged collections after load

Jörg Schaible-2
Hi Алексеев,

Алексеев Сергей wrote:

>    Hello team. I got some question/problem with XStream.
>
>    I have a class with custom realization of methods equals/hashCode. Such
> objects contains in HashSet collection. Default XStream behavior: create
> object instance, read attributes, read inner fields, return object and add
> it to collection if needed.
>    The problem: due a specific object graph on some step of
>    deserialization
> object was created, added to collection, and after all other fields was
> loaded. After fields was loaded, result of hashCode was changed, causing
> "damage" to collection (because HashSet uses HashMap inside). That happens
> because xstream got object with null fields from cache by 'refference'
> attribute. This does not looks like a bug, but working behavior is not
> ideal.

Well, your object graph has circular references and at therefore it is
simply not possible. At some stage, one of the object cannot be initialized
fully. XStream behaves even here exactly like Java serialization. Let
ConnectionPin and Pin both implement Serializable and make the serialization
algorithm exchangeable:

===================== %< ===================
    private static List<?> serializedWithXStream(List<ConnectionPin> inList)
{
        /* toXML ---------------------------------------- */
        XStream xstream = new XStream();
        xstream.setMode(XStream.ID_REFERENCES);
        xstream.autodetectAnnotations(true);

        String serialization = xstream.toXML(inList);
        System.out.println(serialization);

        /* fromXML ---------------------------------------- */

        System.out.println("\nLoad from xml\n");

        List<?> list = (List<?>)xstream.fromXML(serialization);

        System.out.println();
        return list;
    }

    private static List<?>
serializedWithJavaSerialization(List<ConnectionPin> inList) {
        try {
            /* to bytes ---------------------------------------- */
            ByteArrayOutputStream outputStream = new
ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(outputStream);
            oos.writeObject(inList);
            oos.close();

            /* fromXML ---------------------------------------- */

            System.out.println("\nLoad from bytes\n");

            ByteArrayInputStream inputStream = new
ByteArrayInputStream(outputStream.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(inputStream);

            List<?> list = (List<?>)ois.readObject();

            System.out.println();
            return list;
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
===================== %< ===================

If you run your application now calling one time the serialization with
XStream and one time with Java serialization, you'll see, that you have
exactly the same problems, because it is simply not possible to have the
referring and the referred element initialized completely at the same time.

> In my project I fixed it by restoring collection (just recreate it with
> same objects) in readResolve of other object (1 level upward in graph).
>    There are other problem: calling readResolve does not guarantee that
>    all
> fields of 'this' object (including sub objects in fields) was fully
> deserialized at moment of call. That why I cant fix collection  in
> readResolve of object with HashSet or HashMap and should do it in other
> place.
>
>    There are attach file that nicely describes a situation.

You will have to break the circle for serialization and restore it at
deserialization time. You may consider readResolve and writeReplace methods
that actually write/read different replacement objects or play with
Externalizable or ...

Cheers,
Jörg


---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Custom hashCode and damaged collections after load

Jörg Schaible-4
Hi,

Jörg Schaible wrote:

> Hi Алексеев,
>
> Алексеев Сергей wrote:
>
>>    Hello team. I got some question/problem with XStream.
>>
>>    I have a class with custom realization of methods equals/hashCode.
>>    Such
>> objects contains in HashSet collection. Default XStream behavior: create
>> object instance, read attributes, read inner fields, return object and
>> add it to collection if needed.
>>    The problem: due a specific object graph on some step of
>>    deserialization
>> object was created, added to collection, and after all other fields was
>> loaded. After fields was loaded, result of hashCode was changed, causing
>> "damage" to collection (because HashSet uses HashMap inside). That
>> happens because xstream got object with null fields from cache by
>> 'refference' attribute. This does not looks like a bug, but working
>> behavior is not ideal.
>
> Well, your object graph has circular references and at therefore it is
> simply not possible. At some stage, one of the object cannot be
> initialized fully. XStream behaves even here exactly like Java
> serialization. Let ConnectionPin and Pin both implement Serializable and
> make the serialization algorithm exchangeable:
>
> ===================== %< ===================
>     private static List<?> serializedWithXStream(List<ConnectionPin>
>     inList)
> {
>         /* toXML ---------------------------------------- */
>         XStream xstream = new XStream();
>         xstream.setMode(XStream.ID_REFERENCES);
>         xstream.autodetectAnnotations(true);
>
>         String serialization = xstream.toXML(inList);
>         System.out.println(serialization);
>
>         /* fromXML ---------------------------------------- */
>
>         System.out.println("\nLoad from xml\n");
>
>         List<?> list = (List<?>)xstream.fromXML(serialization);
>
>         System.out.println();
>         return list;
>     }
>
>     private static List<?>
> serializedWithJavaSerialization(List<ConnectionPin> inList) {
>         try {
>             /* to bytes ---------------------------------------- */
>             ByteArrayOutputStream outputStream = new
> ByteArrayOutputStream();
>             ObjectOutputStream oos = new ObjectOutputStream(outputStream);
>             oos.writeObject(inList);
>             oos.close();
>
>             /* fromXML ---------------------------------------- */
>
>             System.out.println("\nLoad from bytes\n");
>
>             ByteArrayInputStream inputStream = new
> ByteArrayInputStream(outputStream.toByteArray());
>             ObjectInputStream ois = new ObjectInputStream(inputStream);
>
>             List<?> list = (List<?>)ois.readObject();
>
>             System.out.println();
>             return list;
>         } catch (IOException e) {
>             throw new RuntimeException(e);
>         } catch (ClassNotFoundException e) {
>             throw new RuntimeException(e);
>         }
>     }
> ===================== %< ===================
>
> If you run your application now calling one time the serialization with
> XStream and one time with Java serialization, you'll see, that you have
> exactly the same problems, because it is simply not possible to have the
> referring and the referred element initialized completely at the same
> time.
>
>> In my project I fixed it by restoring collection (just recreate it with
>> same objects) in readResolve of other object (1 level upward in graph).
>>    There are other problem: calling readResolve does not guarantee that
>>    all
>> fields of 'this' object (including sub objects in fields) was fully
>> deserialized at moment of call. That why I cant fix collection  in
>> readResolve of object with HashSet or HashMap and should do it in other
>> place.
>>
>>    There are attach file that nicely describes a situation.
>
> You will have to break the circle for serialization and restore it at
> deserialization time. You may consider readResolve and writeReplace
> methods that actually write/read different replacement objects or play
> with Externalizable or ...

actually it is enough to prevent the usage of the bogus hash code. Simply
use an appropriate Set implementation:

===================== %< ===================

...

  public static class ConnectionPin
  {
    public Set<Connection> inConnections = new ArraySet<Main.Connection>();
    public Set<Connection> outConnections = new ArraySet<Main.Connection>();
    ...
  }

  @XStreamAlias( "array-set" )
  @XStreamConverter( CollectionConverter.class )
  public static class ArraySet<T> extends AbstractSet<T> implements Set<T> {

    private List<T> list = new ArrayList<T>();

    @Override
    public boolean add(T e)
    {
      return list.contains(e) ? false : list.add(e);
    }

    @Override
    public int size()
    {
      return list.size();
    }

    @Override
    public Iterator<T> iterator()
    {
      return list.iterator();
    }
  }

===================== %< ===================

While in this use case a ConnectionPin instance cannot be complete in
readResolve (which is no longer required anyway), the sets are setup
correctly now.

Cheers,
Jörg


---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email