All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.objectfabric.ImmutableWriter Maven / Gradle / Ivy

/**
 * This file is part of ObjectFabric (http://objectfabric.org).
 *
 * ObjectFabric is licensed under the Apache License, Version 2.0, the terms
 * of which may be found at http://www.apache.org/licenses/LICENSE-2.0.html.
 * 
 * Copyright ObjectFabric Inc.
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

package org.objectfabric;

import org.objectfabric.ThreadAssert.SingleThreaded;

@SingleThreaded
class ImmutableWriter extends Continuation {

    private Buff _buff;

    // TODO add caches e.g. for long, String (add UID?) to avoid sending again

    // Debug

    static final int DEBUG_OVERHEAD;

    private static boolean _checkCommunications = true;

    protected ImmutableWriter(List interruptionStack) {
        super(interruptionStack);
    }

    final Buff getBuff() {
        return _buff;
    }

    final void setBuff(Buff buff) {
        _buff = buff;
    }

    //

    void reset() {
        if (Debug.COMMUNICATIONS && getCheckCommunications())
            ThreadAssert.getOrCreateCurrent().resetWriterDebugCounter(this);
    }

    private final int remaining() {
        /*
         * TODO: always say 0 the first time to test all interruption sites.
         */
        return _buff.limit() - _buff.position();
    }

    private final void putBoolean(boolean value) {
        _buff.putByte(value ? (byte) 1 : (byte) 0);
    }

    final void putByte(byte value) {
        _buff.putByte(value);
    }

    //

    /*
     * Fixed lengths.
     */

    private static final int BYTE_LENGTH = 1;

    public final boolean canWriteByte() {
        int needed = BYTE_LENGTH;

        if (Debug.COMMUNICATIONS && getCheckCommunications()) {
            Helper.instance().setExpectedClass(Immutable.BYTE_INDEX);
            needed += ImmutableWriter.DEBUG_OVERHEAD;
        }

        return remaining() >= needed;
    }

    public final void writeByte(byte value) {
        writeByte(value, 0);
    }

    public final void writeByte(byte value, int tag) {
        if (Debug.COMMUNICATIONS && getCheckCommunications())
            writeDebugInfo(Immutable.BYTE_INDEX, tag);

        _buff.putByte(value);
    }

    public final boolean canWriteByteBoxed() {
        int needed = BOOLEAN_LENGTH + BYTE_LENGTH;

        if (Debug.COMMUNICATIONS && getCheckCommunications()) {
            Helper.instance().setExpectedClass(Immutable.BYTE_BOXED_INDEX);
            needed += ImmutableWriter.DEBUG_OVERHEAD;
        }

        return remaining() >= needed;
    }

    public final void writeByteBoxed(Byte value) {
        if (Debug.COMMUNICATIONS && getCheckCommunications())
            writeDebugInfo(Immutable.BYTE_BOXED_INDEX, 0);

        if (value == null) // Marker
            putBoolean(false);
        else {
            putBoolean(true);
            _buff.putByte(value); // Marker
        }
    }

    //

    private static final int BOOLEAN_LENGTH = 1;

    public final boolean canWriteBoolean() {
        int needed = BOOLEAN_LENGTH;

        if (Debug.COMMUNICATIONS && getCheckCommunications()) {
            Helper.instance().setExpectedClass(Immutable.BOOLEAN_INDEX);
            needed += ImmutableWriter.DEBUG_OVERHEAD;
        }

        return remaining() >= needed;
    }

    public final void writeBoolean(boolean value) {
        if (Debug.COMMUNICATIONS && getCheckCommunications())
            writeDebugInfo(Immutable.BOOLEAN_INDEX, 0);

        putBoolean(value);
    }

    public final boolean canWriteBooleanBoxed() {
        int needed = BOOLEAN_LENGTH + BOOLEAN_LENGTH;

        if (Debug.COMMUNICATIONS && getCheckCommunications()) {
            Helper.instance().setExpectedClass(Immutable.BOOLEAN_BOXED_INDEX);
            needed += ImmutableWriter.DEBUG_OVERHEAD;
        }

        return remaining() >= needed;
    }

    public final void writeBooleanBoxed(Boolean value) {
        if (Debug.COMMUNICATIONS && getCheckCommunications())
            writeDebugInfo(Immutable.BOOLEAN_BOXED_INDEX, 0);

        if (value == null) // Marker
            putBoolean(false);
        else {
            putBoolean(true);
            putBoolean(value); // Marker
        }
    }

    //

    private static final int SHORT_LENGTH = Short.SIZE / 8;

    public final boolean canWriteShort() {
        int needed = SHORT_LENGTH;

        if (Debug.COMMUNICATIONS && getCheckCommunications()) {
            Helper.instance().setExpectedClass(Immutable.SHORT_INDEX);
            needed += ImmutableWriter.DEBUG_OVERHEAD;
        }

        return remaining() >= needed;
    }

    public final void writeShort(short value) {
        if (Debug.COMMUNICATIONS && getCheckCommunications())
            writeDebugInfo(Immutable.SHORT_INDEX, 0);

        _buff.putShort(value);
    }

    public final boolean canWriteShortBoxed() {
        int needed = BOOLEAN_LENGTH + SHORT_LENGTH;

        if (Debug.COMMUNICATIONS && getCheckCommunications()) {
            Helper.instance().setExpectedClass(Immutable.SHORT_BOXED_INDEX);
            needed += ImmutableWriter.DEBUG_OVERHEAD;
        }

        return remaining() >= needed;
    }

    public final void writeShortBoxed(Short value) {
        if (Debug.COMMUNICATIONS && getCheckCommunications())
            writeDebugInfo(Immutable.SHORT_BOXED_INDEX, 0);

        if (value == null) // Marker
            putBoolean(false);
        else {
            putBoolean(true);
            _buff.putShort(value); // Marker
        }
    }

    //

    private static final int CHARACTER_LENGTH = Character.SIZE / 8;

    public final boolean canWriteCharacter() {
        int needed = CHARACTER_LENGTH;

        if (Debug.COMMUNICATIONS && getCheckCommunications()) {
            Helper.instance().setExpectedClass(Immutable.CHARACTER_INDEX);
            needed += ImmutableWriter.DEBUG_OVERHEAD;
        }

        return remaining() >= needed;
    }

    public final void writeCharacter(char value) {
        if (Debug.COMMUNICATIONS && getCheckCommunications())
            writeDebugInfo(Immutable.CHARACTER_INDEX, 0);

        _buff.putChar(value);
    }

    public final boolean canWriteCharacterBoxed() {
        int needed = BOOLEAN_LENGTH + CHARACTER_LENGTH;

        if (Debug.COMMUNICATIONS && getCheckCommunications()) {
            Helper.instance().setExpectedClass(Immutable.CHARACTER_BOXED_INDEX);
            needed += ImmutableWriter.DEBUG_OVERHEAD;
        }

        return remaining() >= needed;
    }

    public final void writeCharacterBoxed(Character value) {
        if (Debug.COMMUNICATIONS && getCheckCommunications())
            writeDebugInfo(Immutable.CHARACTER_BOXED_INDEX, 0);

        if (value == null) // Marker
            putBoolean(false);
        else {
            putBoolean(true);
            _buff.putChar(value); // Marker
        }
    }

    //

    private static final int INTEGER_LENGTH = Integer.SIZE / 8;

    public final boolean canWriteInteger() {
        int needed = INTEGER_LENGTH;

        if (Debug.COMMUNICATIONS && getCheckCommunications()) {
            Helper.instance().setExpectedClass(Immutable.INTEGER_INDEX);
            needed += ImmutableWriter.DEBUG_OVERHEAD;
        }

        return remaining() >= needed;
    }

    public final void writeInteger(int value) {
        if (Debug.COMMUNICATIONS && getCheckCommunications())
            writeDebugInfo(Immutable.INTEGER_INDEX, 0);

        _buff.putInt(value);
    }

    public final boolean canWriteIntegerBoxed() {
        int needed = BOOLEAN_LENGTH + INTEGER_LENGTH;

        if (Debug.COMMUNICATIONS && getCheckCommunications()) {
            Helper.instance().setExpectedClass(Immutable.INTEGER_BOXED_INDEX);
            needed += ImmutableWriter.DEBUG_OVERHEAD;
        }

        return remaining() >= needed;
    }

    public final void writeIntegerBoxed(Integer value) {
        if (Debug.COMMUNICATIONS && getCheckCommunications())
            writeDebugInfo(Immutable.INTEGER_BOXED_INDEX, 0);

        if (value == null) // Marker
            putBoolean(false);
        else {
            putBoolean(true);
            _buff.putInt(value); // Marker
        }
    }

    //

    private static final int LONG_LENGTH = Long.SIZE / 8;

    public final boolean canWriteLong() {
        int needed = LONG_LENGTH;

        if (Debug.COMMUNICATIONS && getCheckCommunications()) {
            Helper.instance().setExpectedClass(Immutable.LONG_INDEX);
            needed += ImmutableWriter.DEBUG_OVERHEAD;
        }

        return remaining() >= needed;
    }

    public final void writeLong(long value) {
        if (Debug.COMMUNICATIONS && getCheckCommunications())
            writeDebugInfo(Immutable.LONG_INDEX, 0);

        _buff.putLong(value);
    }

    public final boolean canWriteLongBoxed() {
        int needed = BOOLEAN_LENGTH + LONG_LENGTH;

        if (Debug.COMMUNICATIONS && getCheckCommunications()) {
            Helper.instance().setExpectedClass(Immutable.LONG_BOXED_INDEX);
            needed += ImmutableWriter.DEBUG_OVERHEAD;
        }

        return remaining() >= needed;
    }

    public final void writeLongBoxed(Long value) {
        if (Debug.COMMUNICATIONS && getCheckCommunications())
            writeDebugInfo(Immutable.LONG_BOXED_INDEX, 0);

        if (value == null) // Marker
            putBoolean(false);
        else {
            putBoolean(true);
            _buff.putLong(value); // Marker
        }
    }

    //

    private static final int FLOAT_LENGTH = Float.SIZE / 8;

    public final boolean canWriteFloat() {
        int needed = FLOAT_LENGTH;

        if (Debug.COMMUNICATIONS && getCheckCommunications()) {
            Helper.instance().setExpectedClass(Immutable.FLOAT_INDEX);
            needed += ImmutableWriter.DEBUG_OVERHEAD;
        }

        return remaining() >= needed;
    }

    public final void writeFloat(float value) {
        if (Debug.COMMUNICATIONS && getCheckCommunications())
            writeDebugInfo(Immutable.FLOAT_INDEX, 0);

        int i = Platform.get().floatToInt(value);
        _buff.putInt(i);
    }

    public final boolean canWriteFloatBoxed() {
        int needed = BOOLEAN_LENGTH + FLOAT_LENGTH;

        if (Debug.COMMUNICATIONS && getCheckCommunications()) {
            Helper.instance().setExpectedClass(Immutable.FLOAT_BOXED_INDEX);
            needed += ImmutableWriter.DEBUG_OVERHEAD;
        }

        return remaining() >= needed;
    }

    public final void writeFloatBoxed(Float value) {
        if (Debug.COMMUNICATIONS && getCheckCommunications())
            writeDebugInfo(Immutable.FLOAT_BOXED_INDEX, 0);

        if (value == null) // Marker
            putBoolean(false);
        else {
            putBoolean(true);
            int i = Platform.get().floatToInt(value); // Marker
            _buff.putInt(i);
        }
    }

    //

    private static final int DOUBLE_LENGTH = Double.SIZE / 8;

    public final boolean canWriteDouble() {
        int needed = DOUBLE_LENGTH;

        if (Debug.COMMUNICATIONS && getCheckCommunications()) {
            Helper.instance().setExpectedClass(Immutable.DOUBLE_INDEX);
            needed += ImmutableWriter.DEBUG_OVERHEAD;
        }

        return remaining() >= needed;
    }

    public final void writeDouble(double value) {
        if (Debug.COMMUNICATIONS && getCheckCommunications())
            writeDebugInfo(Immutable.DOUBLE_INDEX, 0);

        long l = Platform.get().doubleToLong(value);
        _buff.putLong(l);
    }

    public final boolean canWriteDoubleBoxed() {
        int needed = BOOLEAN_LENGTH + DOUBLE_LENGTH;

        if (Debug.COMMUNICATIONS && getCheckCommunications()) {
            Helper.instance().setExpectedClass(Immutable.DOUBLE_BOXED_INDEX);
            needed += ImmutableWriter.DEBUG_OVERHEAD;
        }

        return remaining() >= needed;
    }

    public final void writeDoubleBoxed(Double value) {
        if (Debug.COMMUNICATIONS && getCheckCommunications())
            writeDebugInfo(Immutable.DOUBLE_BOXED_INDEX, 0);

        if (value == null) // Marker
            putBoolean(false);
        else {
            putBoolean(true);
            long l = Platform.get().doubleToLong(value); // Marker
            _buff.putLong(l);
        }
    }

    //

    private static final int DATE_LENGTH = LONG_LENGTH;

    public final boolean canWriteDate() {
        int needed = DATE_LENGTH;

        if (Debug.COMMUNICATIONS && getCheckCommunications()) {
            Helper.instance().setExpectedClass(Immutable.DATE_INDEX);
            needed += ImmutableWriter.DEBUG_OVERHEAD;
        }

        return remaining() >= needed;
    }

    public final void writeDate(java.util.Date value) {
        if (Debug.COMMUNICATIONS && getCheckCommunications())
            writeDebugInfo(Immutable.DATE_INDEX, 0);

        if (value == null) // Marker
            _buff.putLong(-1);
        else
            _buff.putLong(value.getTime() * 10000 + 621355968000000000L);
    }

    /*
     * Non fixed lengths.
     */

    public final void writeString(String value) {
        writeString(value, 0, true);
    }

    public final void writeString(String value, int tag) {
        writeString(value, tag, true);
    }

    /*
     * Interruptible string encoding with null & EOF codes to avoid sending length.
     */

    static final int STRING_ENC_DOES_NOT_FIT_ON_1_BYTE_MASK = 1 << 7;

    static final int STRING_ENC_DOES_NOT_FIT_ON_2_BYTES_MASK = 1 << 6;

    static final int STRING_ENC_EOF_MASK = 1 << 5;

    static final int STRING_ENC_NULL_MASK = 1 << 4;

    private final void writeString(String value, int tag, boolean debug) {
        int index = Debug.COMMUNICATIONS && getCheckCommunications() && debug ? -1 : 0;

        if (interrupted())
            index = resumeInt();

        if (Debug.COMMUNICATIONS && getCheckCommunications() && debug) {
            if (index < 0) {
                if (!canWriteDebugInfo()) {
                    interruptInt(-1);
                    return;
                }

                Helper.instance().setExpectedClass(Immutable.STRING_INDEX);
                writeDebugInfo(Immutable.STRING_INDEX, tag);
                index = 0;
            }
        }

        if (value == null) {
            if (remaining() == 0) {
                interruptInt(0);
                return;
            }

            int mask = STRING_ENC_DOES_NOT_FIT_ON_1_BYTE_MASK;
            mask |= STRING_ENC_DOES_NOT_FIT_ON_2_BYTES_MASK;
            mask |= STRING_ENC_EOF_MASK;
            mask |= STRING_ENC_NULL_MASK;
            _buff.putByte((byte) mask);
            return;
        }

        for (;;) {
            if (index == value.length()) {
                if (remaining() == 0) {
                    interruptInt(index);
                    return;
                }

                int mask = STRING_ENC_DOES_NOT_FIT_ON_1_BYTE_MASK;
                mask |= STRING_ENC_DOES_NOT_FIT_ON_2_BYTES_MASK;
                mask |= STRING_ENC_EOF_MASK;
                _buff.putByte((byte) mask);
                return;
            }

            char c = value.charAt(index);

            if (c < 128) { // Char fits in 7 bits -> 1 byte
                if (remaining() == 0) {
                    interruptInt(index);
                    return;
                }

                _buff.putByte((byte) c);
            } else if (c < 16384) { // Char fits in 14 bits -> 2 bytes
                if (remaining() < 2) {
                    interruptInt(index);
                    return;
                }

                _buff.putByte((byte) (STRING_ENC_DOES_NOT_FIT_ON_1_BYTE_MASK | (c >> 8)));
                _buff.putByte((byte) (c & 0xff));
            } else { // Others -> 3 bytes
                if (remaining() < 3) {
                    interruptInt(index);
                    return;
                }

                int mask = STRING_ENC_DOES_NOT_FIT_ON_1_BYTE_MASK;
                mask |= STRING_ENC_DOES_NOT_FIT_ON_2_BYTES_MASK;
                _buff.putByte((byte) mask);
                _buff.putChar(c);
            }

            index++;
        }
    }

    //

    public final void writeBinary(byte[] value) {
        writeBinary(value, Immutable.BINARY_INDEX);
    }

    private final void writeBinary(byte[] value, int classId) {
        int index = Debug.COMMUNICATIONS && getCheckCommunications() ? -2 : -1;

        if (interrupted())
            index = resumeInt();

        if (Debug.COMMUNICATIONS && getCheckCommunications()) {
            if (index == -2) {
                if (!canWriteDebugInfo()) {
                    interruptInt(-2);
                    return;
                }

                Helper.instance().setExpectedClass(classId);
                writeDebugInfo(classId, 0);
            }
        }

        if (index < 0) {
            if (remaining() < INTEGER_LENGTH) {
                interruptInt(-1);
                return;
            }

            if (value == null) {
                _buff.putInt(-1);
                return;
            }

            _buff.putInt(value.length);
            index = 0;
        }

        // TODO use put(byte[])

        for (; index < value.length; index++) {
            if (remaining() == 0) {
                interruptInt(index);
                return;
            }

            _buff.putByte(value[index]);
        }
    }

    //

    public final void writeBigInteger(java.math.BigInteger value) {
        if (value == null) // Marker
            writeBinary(null, Immutable.BIG_INTEGER_INDEX);
        else
            writeBinary(value.toByteArray(), Immutable.BIG_INTEGER_INDEX);
    }

    //

    public final void writeDecimal(java.math.BigDecimal value) {
        if (value == null) // Marker
            writeBinary(null, Immutable.DECIMAL_INDEX);
        else {
            byte[] array;

            if (interrupted())
                array = (byte[]) resume();
            else {
                byte[] t = value.unscaledValue().toByteArray();
                array = new byte[t.length + 4];
                Platform.arraycopy(t, 0, array, 0, t.length);
                int scale = value.scale();
                array[array.length - 4] = (byte) (scale & 0xff);
                array[array.length - 3] = (byte) ((scale >>> 8) & 0xff);
                array[array.length - 2] = (byte) ((scale >>> 16) & 0xff);
                array[array.length - 1] = (byte) ((scale >>> 24) & 0xff);
            }

            writeBinary(array, Immutable.DECIMAL_INDEX);

            if (interrupted())
                interrupt(array);
        }
    }

    // Debug

    static boolean getCheckCommunications() {
        if (!Debug.COMMUNICATIONS)
            throw new IllegalStateException();

        return _checkCommunications;
    }

    static void setCheckCommunications(boolean value) {
        if (!Debug.COMMUNICATIONS)
            throw new IllegalStateException();

        _checkCommunications = value;
    }

    private final boolean canWriteDebugInfo() {
        if (!Debug.COMMUNICATIONS || !getCheckCommunications())
            throw new IllegalStateException();

        return remaining() >= DEBUG_OVERHEAD;
    }

    static {
        int value = 0;
        value += LONG_LENGTH; // debugCounter
        value += BYTE_LENGTH; // classId
        value += INTEGER_LENGTH; // tag
        DEBUG_OVERHEAD = value;
    }

    private final void writeDebugInfo(int classId, int tag) {
        if (!Debug.COMMUNICATIONS || !getCheckCommunications())
            throw new IllegalStateException();

        if (Debug.COMMUNICATIONS_LOG_ALL) {
            String name = Immutable.ALL.get(classId).toString();
            long counter = ThreadAssert.getOrCreateCurrent().getWriterDebugCounter(this);
            String c = Platform.get().simpleClassName(this);
            Log.write(c + ", " + counter + ", class: " + name + ", tag: " + tag);
        }

        Debug.assertion(classId == Helper.instance().getExpectedClass());
        long debugCounter = ThreadAssert.getOrCreateCurrent().getAndIncrementWriterDebugCounter(this);
        _buff.putLong(debugCounter);
        _buff.putByte((byte) classId);
        _buff.putInt(tag);
    }
}