package de.renew.util;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.StreamCorruptedException;
import java.util.EmptyStackException;
import java.util.Enumeration;
import java.util.Vector;


/** <p>
 * Adds to the classic ObjectOutputStream used to serialize
 * objects the feature to delay the serialization of particular
 * objects.
 * The intention of this feature is to cut down the recursion
 * depth on serialization, as delayed objects (and the part of
 * the serialization tree rooted in their fields) are written
 * from the root level of serialization.
 * </p>
 * <p>
 * The object containing the fields which should be delayed,
 * must declare these fields transient and implement the method:
 * </p>
 * <pre>
 * private void writeObject(ObjectOutputStream out) throws IOException {
 *     out.defaultWriteObject();
 *     ((RenewObjectOutputStream)out).delayedWriteObject(aField, this);
 * }
 * </pre>
 * <p>
 * The delayed written object will not be put into the stream
 * immediately, but remembered until a call to the method
 * <code>writeDelayedObjects()</code> or <code>close()</code>
 * appends all remembered objects to the stream.
 * The second parameter to the <code>delayedWriteObject</code>
 * method (<code>this</code>) is needed to allow to reassign the
 * written object to the object containing the field on
 * deserialization.
 * </p>
 * <p>
 * The stream produced by this class should be read by an instance
 * of <code>RenewObjectInputStream</code>. Be sure to read the
 * documentation of that class, too.
 * </p>
 * <p>
 * <b>Additional feature:</b>
 * </p>
 * <p>
 * Objects serialized by a <code>RenewObjectOutputStream</code>
 * instance can document the serialization progress by using the
 * methods <code>beginDomain()</code> and <code>endDomain()</code>
 * in their private <code>writeObject()</code> methods.
 * If the call to <code>endDomain()</code> is omitted when an
 * exception is raised, you obtain a protocol much like the
 * stack trace generated with exceptions, but containing objects
 * instead of class names.
 * The stream already uses this feature when delayed fields are
 * written: the delayed field owner is automatically documented
 * within the protocol.
 * </p>
 *
 *
 * RenewObjectOutputStream.java
 * <p>
 * Created: Tue Feb  8  2000
 *
 * @see RenewObjectInputStream
 * @see DelayedFieldOwner
 *
 * @author Michael Duvigneau
 */
public class RenewObjectOutputStream extends ObjectOutputStream {
    /**
     * create logger for the class RenewObjectOutputStream.
     */
    public final static org.apache.log4j.Logger logger =
        org.apache.log4j.Logger.getLogger(RenewObjectOutputStream.class);
    /**
     * Section label for the delayed objects
     */
    protected final static String SECTIONLABEL = "Delayed:";
    /** The Vector of DelayedField. **/
    private Vector<DelayedField> _delayedObjects;
    private final java.util.Stack<Object> _objectTrace = new java.util.Stack<>();

    /**
     * Constructor for RenewObjectOutputStream,
     * Creates a new <code>RenewObjectOutputStream</code> instance with
     * Vector of delayed objects with the default initial capacity of 10.
     *
     * @param out the stream to be enhanced by <code>RenewObjectOutputStream</code>'s special features
     * @exception  IOException on input error.
     * @see RenewObjectOutputStream class comment
     **/
    public RenewObjectOutputStream(OutputStream out) throws IOException {
        super(out);
        _delayedObjects = new Vector<>();
    }

    /**
     * Appends the given deserialized Objects and their deserialized fields.
     *
     * @param obj The obj to be added to DelayedField vector.
     * @param owner Deserialized fields to be reassigned to the deserialized objects and added to DelayedField vector.
     * @see RenewObjectOutputStream class comment
     **/

    public void delayedWriteObject(Object obj, DelayedFieldOwner owner) {
        // logger.debug("Delayed: "+obj);
        _delayedObjects.addElement(new DelayedField(obj, owner));
    }

    /**
     * Write objects delayed to the Stream afterward and document the serialization progress.
     *
     * @exception IOException on input error.
     * @see RenewObjectOutputStream class comment
     **/

    public void writeDelayedObjects() throws IOException {
        int objectCount;
        Enumeration<DelayedField> objects;
        Object obj;


        // logger.debug("Writing delayed objects: START");
        // Start the delayed objects section by writing
        // the section label
        beginDomain(SECTIONLABEL);
        super.writeObject(SECTIONLABEL);

        while (!_delayedObjects.isEmpty()) {
            // Get Size and Enumeration of delayedObjects and
            // ensure that adding objects while writing the
            // elements does not disturb the Enumeration.
            // The outer while loop ensures that objects
            // delayed while writing the elements are written
            // afterward.
            objectCount = _delayedObjects.size();
            objects = _delayedObjects.elements();
            _delayedObjects = new Vector<>();


            // Write the number of elements to the Stream
            super.writeInt(objectCount);


            // Write all elements of the Enumeration to
            // the stream
            while (objects.hasMoreElements()) {
                obj = objects.nextElement();
                super.writeObject(obj);
            }
        }


        // Finish the delayed objects section by writing
        // an empty field group (e.g. the int 0).
        super.writeInt(0);

        endDomain(SECTIONLABEL);
        // logger.debug("Writing delayed objects: END");
    }

    @Override
    public void close() throws IOException {
        try {
            // Write all remembered Objects not written now.
            // (If there are none, an empty field group 
            // will be written).
            writeDelayedObjects();
        } catch (IOException e) {
            throw new StreamCorruptedException(
                "Exception occurred while writing " + "delayed objects on close: " + e);
        } finally {
            // now, really close the stream
            super.close();
        }
    }

    /**
     * Help <code>writeDelayedObjects()</code> to start document the serialization progress
     * by push the delayed Object owner on the top of the stack.
     *
     * @param owner the delayed Object owner to be pushed
     * @see RenewObjectOutputStream class comment
     **/
    public void beginDomain(Object owner) {
        _objectTrace.push(owner);


    }

    /**
     * Help <code>writeDelayedObjects()</code> to end document the serialization progress
     * by compare the delayed Object owner to the owner at the beginning of the document the serialization progress.
     *
     * @param owner the delayed Object owner to be compared und removed.
     * @see RenewObjectOutputStream class comment
     **/
    public void endDomain(Object owner) {
        try {
            if (_objectTrace.peek() == owner) {
                _objectTrace.pop();
            } else {
                // ignore
                logger.error("End domain by wrong owner requested by " + owner);
                logger.error("Top of stack is " + _objectTrace.peek());
                logger.error("Owner resides " + _objectTrace.search(owner) + " from top of stack");
            }
        } catch (EmptyStackException e) {
            // ignore
            logger.error("End domain on empty stack requested by " + owner);
        }


    }

    /**
     *  Returns a copy of the stack trace of objects.
     * {@link java.util.Stack}
     * @return copy of the stack trace of objects.
     * @see RenewObjectOutputStream class comment
     **/
    @SuppressWarnings("unchecked")
    public java.util.Stack<Object> getDomainTrace() {
        return (java.util.Stack<Object>) _objectTrace.clone();
    }
}