package anastore.util;

import java.io.*;
import java.util.*;

import static anastore.util.StreamExpectations.*;

/**
 * A specialized object output stream that uses a vastly more
 * efficient encoding of class information than the standard Java
 * serialization protocol.  This encoding is far less defensive, and
 * operates on the assumption that the reader of the input stream will
 * have the same class definitions for objects contained in the
 * stream.
 *<p>
 * Streams written by this output stream should only be consumed by a
 * {@link FastObjectInputStream}.
 */
public class FastObjectOutputStream extends ObjectOutputStream
{
    private static final boolean LOG = false;
    private static final Log log = new Log(LOG);

    public FastObjectOutputStream(OutputStream out)
        throws IOException
    {
        super(out);
    }

    private StreamExpectations _exp = new StreamExpectations();

    /**
     * Map for tracking our own handles for descriptors that persist
     * over stream resets, since ObjectStreamClass instances are
     * immutable.
     */
    private final Map<ObjectStreamClass, Integer> _refs =
        new IdentityHashMap<ObjectStreamClass, Integer>();
    private int _nextId = 0;

    private void writeDescriptor(byte nonref, byte ref,
                                 ObjectStreamClass desc)
        throws IOException
    {
        if (_refs.containsKey(desc)) {
            writeByte(ref);
            writeInt(_refs.get(desc));
        } else {
            writeByte(nonref);
            writeUTF(desc.forClass().getName());
            _refs.put(desc, _nextId);
            ++_nextId;
        }
    }

    protected void writeClassDescriptor(ObjectStreamClass desc)
        throws IOException
    {
        boolean addRef = false;
        ObjectStreamClass expect = _exp.takeExpectation();
        if (expect == desc) {
            if (LOG)
                log.println("Writing expected descriptor " + desc.getName());
            writeByte(CODE_EXPECTED);
        } else if (expect != null &&
                   expect.forClass().isAssignableFrom(desc.forClass())) {
            if (LOG)
                log.println("Writing subclass descriptor " + desc.getName());
            writeDescriptor(CODE_SUBCLASS, CODE_SUBCLASS_REF, desc);
        } else if (_exp.canExpect(desc)) {
            int skip = _exp.skipOver(desc);
            if (LOG)
                log.println("Writing skip to descriptor " + skip +
                            " (" + desc.getName() + ")");
            writeByte(CODE_SKIP);
            writeInt(skip);
        } else {
            _exp.clear();
            if (LOG) {
                if (expect == null)
                    log.println("Writing root descriptor " + desc.getName());
                else
                    log.println("Writing unexpected descriptor " + desc.getName() +
                                " (expected " + expect.getName() + ")");
            }
            writeDescriptor(CODE_UNEXPECTED, CODE_UNEXPECTED_REF, desc);
        }
        _exp.addExpectations(desc);
    }

    /**
     * Only for testing.
     */
    StreamExpectations getExpectations()
    {
        return _exp;
    }
}
