StateSaver.java

package nl.tudelft.simulation.language.reflection;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Set;

import org.djutils.reflection.ClassUtil;

import nl.tudelft.simulation.language.DsolException;

/**
 * StateSaver can serialize a full state of a single object, including the fields of its superclass, to an object. Later, this
 * object can be used to reset the full state of the object to the old value. This is useful when doing rollback in a
 * simulation; the state of objects can be rolled back to their old values, including random number generators, which will reset
 * their seed to the old value. Be careful with objects with shared pointers to e.g., collections, as the restoreState() on a
 * single object might lead to the duplication of the shared objects indicated by these pointers. When the rollback is needed
 * for an entire simulation, use XmlUtil, GSON, or another library to serialize / deserialize an entire simulation model at
 * once. <br>
 * <p>
 * Copyright (c) 2018 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See for
 * project information <a href="https://simulation.tudelft.nl/dsol/manual/" target="_blank">DSOL Manual</a>. The DSOL
 * project is distributed under a three-clause BSD-style license, which can be found at
 * <a href="https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">DSOL License</a>.
 * </p>
 * @author <a href="https://github.com/averbraeck" target="_blank"> Alexander Verbraeck</a>
 */
public final class StateSaver
{
    /** */
    private StateSaver()
    {
        // utility class
    }

    /**
     * Save the state of a single object into another object. Fields of the superclass are included. The state save is a deep
     * copy, using the writeObject() method of serialization.
     * @param object Object; the object to save the state from
     * @return the state packed into a memory object
     * @throws DsolException on serialization error
     */
    public static byte[] saveState(final Object object) throws DsolException
    {
        byte[] state = null;
        try
        {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(bos);
            out.writeObject(object);
            state = bos.toByteArray();
            out.close();
            bos.close();
        }
        catch (IOException exception)
        {
            throw new DsolException(exception);
        }
        return state;
    }

    /**
     * Retrieve the earlier saved state of a single object and write it into an object (which could be a clone or the original
     * object). Fields of the superclass are included. The state retrieval uses all information from a deep copy, using the
     * readObject() method of deserialization to fill the target object.
     * @param target Object; the target object to write the deserialized information into
     * @param state Object; the earlier saved state to write
     * @throws DsolException on deserialization error
     */
    public static void restoreState(final Object target, final byte[] state) throws DsolException
    {
        try
        {
            byte[] byteState = state;
            ByteArrayInputStream bis = new ByteArrayInputStream(byteState);
            ObjectInputStream in = new ObjectInputStream(bis);
            Object tempObject = in.readObject();
            Set<Field> allFields = ClassUtil.getAllFields(target.getClass());
            for (Field field : allFields)
            {
                if (!Modifier.isTransient(field.getModifiers()) && !Modifier.isStatic(field.getModifiers()))
                {
                    field.setAccessible(true);
                    field.set(target, field.get(tempObject));
                }
            }
            in.close();
            bis.close();
        }
        catch (IOException | ClassNotFoundException | IllegalArgumentException | IllegalAccessException
                | SecurityException exception)
        {
            throw new DsolException(exception);
        }
    }

}