Package com.foundationdb.server.rowdata

Source Code of com.foundationdb.server.rowdata.RowDataBuilder$ValueAdapter

/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package com.foundationdb.server.rowdata;

import com.foundationdb.server.AkServerUtil;
import com.foundationdb.server.rowdata.encoding.EncodingException;
import com.foundationdb.server.types.TExecutionContext;
import com.foundationdb.server.types.TInstance;
import com.foundationdb.server.types.mcompat.mtypes.MBinary;
import com.foundationdb.server.types.mcompat.mtypes.MString;
import com.foundationdb.server.types.value.*;
import com.foundationdb.server.types.value.UnderlyingType;
import com.foundationdb.util.ByteSource;

public final class RowDataBuilder {

    public void startAllocations() {
        state.require(State.NEWLY_CONSTRUCTED);

        // header
        byte[] bytes = rowData.getBytes();
        AkServerUtil.putShort(bytes, fixedWidthSectionOffset + RowData.O_SIGNATURE_A, RowData.SIGNATURE_A);
        AkServerUtil.putInt(bytes, fixedWidthSectionOffset + RowData.O_ROW_DEF_ID, rowDef.getRowDefId());
        AkServerUtil.putShort(bytes, fixedWidthSectionOffset + RowData.O_FIELD_COUNT, rowDef.getFieldCount());
        fixedWidthSectionOffset = fixedWidthSectionOffset + RowData.O_NULL_MAP;
        nullMapOffset = fixedWidthSectionOffset;
        // TODO unloop this?
        for (int index = 0; index < rowDef.getFieldCount(); index += 8) {
            rowData.getBytes()[fixedWidthSectionOffset++] = 0;
        }

        fieldIndex = 0;
        assert fixedWidthSectionOffset == rowData.getRowStartData()
                : fixedWidthSectionOffset + " != " + rowData.getRowStartData();
        state = State.ALLOCATING;
    }

    public void allocate(FieldDef fieldDef, Object object) {
        checkWithinRange(State.ALLOCATING);

        if (fieldDef != rowDef.getFieldDef(fieldIndex)) {
            throw new IllegalStateException(fieldDef + " but expected " + rowDef.getFieldDef(fieldIndex));
        }
        if (fieldDef.getRowDef() != rowDef) {
            throw new IllegalStateException("expected RowDef " + rowDef + " but found " + fieldDef.getRowDef());
        }

        final int fieldFixedWidth;
        if (fieldDef.isFixedSize()) {
            fieldFixedWidth = (object == null)
                    ? 0
                    : fieldDef.getEncoding().widthFromObject(fieldDef, object);
        } else {
            int fieldMax = fieldDef.getMaxStorageSize();
            vmax += fieldMax;
            if (object == null) {
                fieldFixedWidth = 0;
            }
            else {
                final int varWidth;
                try {
                    varWidth = fieldDef.getEncoding().widthFromObject(fieldDef, object);
                    vlength += varWidth;
                } catch (Exception e) {
                    throw EncodingException.dueTo(e);
                }
                if (varWidth > fieldMax) {
                    throw new EncodingException(
                        String.format("Value for field %s has size %s, exceeding maximum allowed: %s",
                                      fieldDef.column(), varWidth, fieldMax));
                }
                fieldFixedWidth = AkServerUtil.varWidth(vmax);
                byte[] bytes = rowData.getBytes();
                switch (fieldFixedWidth) {
                case 0:
                    break;
                case 1:
                    AkServerUtil.putByte(bytes, fixedWidthSectionOffset, (byte) vlength);
                    break;
                case 2:
                    AkServerUtil.putShort(bytes, fixedWidthSectionOffset, (char) vlength);
                    break;
                case 3:
                    AkServerUtil.putMediumInt(bytes, fixedWidthSectionOffset, vlength);
                    break;
                case 4:
                    AkServerUtil.putInt(bytes, fixedWidthSectionOffset, vlength);
                    break;
                default:
                    throw new UnsupportedOperationException("bad width-of-width: " + fieldFixedWidth);
                }
            }
        }

        fieldWidths[fieldIndex] = fieldFixedWidth;
        fixedWidthSectionOffset += fieldFixedWidth;
        ++fieldIndex;
    }

    public void startPuts() {
        state.require(State.ALLOCATING);
        vlength = 0;
        nullRemainingAllocations();
        variableWidthSectionOffset = fixedWidthSectionOffset;
        // rewind to the start of the fixed length portion
        for (int fieldWidth : fieldWidths) {
            fixedWidthSectionOffset -= fieldWidth;
        }
        fieldIndex = 0;
        state = State.PUTTING;
    }

    public void putObject(Object o) {
        FieldDef fieldDef = rowDef.getFieldDef(fieldIndex);
        adapter.objectToSource(o, fieldDef);
        convert(null, adapter, fieldDef);
    }

    private <S,T extends RowDataTarget> void convert(S source, ValueAdapter<S,T> valueAdapter, FieldDef fieldDef) {
        state.require(State.PUTTING);

        byte[] bytes = rowData.getBytes();
        int currFixedWidth = fieldWidths[fieldIndex];

        if (source == null)
            source = valueAdapter.source();
        T target = valueAdapter.target();

        if (valueAdapter.isNull(source)) {
            if (currFixedWidth != 0) {
                throw new IllegalStateException("expected source to give null: " + source);
            }
            target.bind(fieldDef, bytes, nullMapOffset);
            target.putNull();
            if (target.lastEncodedLength() != 0) {
                throw new IllegalStateException("putting a null should have encoded 0 bytes");
            }
        } else if (fieldDef.isFixedSize()) {
            target.bind(fieldDef, bytes, fixedWidthSectionOffset);
            valueAdapter.convert(fieldDef);
            if (target.lastEncodedLength() != currFixedWidth) {
                throw new IllegalStateException("expected to write " + currFixedWidth
                        + " fixed-width byte(s), but wrote " + target.lastEncodedLength());
            }
        } else {
            target.bind(fieldDef, bytes, variableWidthSectionOffset);
            valueAdapter.convert(fieldDef);
            int varWidthExpected = readVarWidth(bytes, currFixedWidth);
            // the stored value (retrieved by readVarWidth) is actually the *cumulative* length; we want just
            // this field's length. So, we'll subtract from this cumulative value the previously-maintained sum of the
            // previous variable-length fields, and use that for our comparison. Once that's done, we'll add this
            // field's length to that cumulative sum.
            varWidthExpected -= vlength;
            if (target.lastEncodedLength() != varWidthExpected) {
                throw new IllegalStateException("expected to write " + varWidthExpected
                        + " variable-width byte(s), but wrote " + target.lastEncodedLength()
                        + " (vlength=" + vlength + "). FieldDef=" + fieldDef + ", value = <" + source + '>');
            }
            vlength += varWidthExpected;
            variableWidthSectionOffset += varWidthExpected;
        }
        fixedWidthSectionOffset += currFixedWidth;

        ++fieldIndex;
    }

    public int finalOffset() {
        state.require(State.PUTTING);
        nullRemainingPuts(adapter);

        // footer
        byte[] bytes = rowData.getBytes();
        AkServerUtil.putShort(bytes, variableWidthSectionOffset, RowData.SIGNATURE_B);
        variableWidthSectionOffset += 6;
        int length = variableWidthSectionOffset - rowData.getRowStart();
        AkServerUtil.putInt(bytes, rowData.getRowStart() + RowData.O_LENGTH_A, length);
        AkServerUtil.putInt(bytes, variableWidthSectionOffset + RowData.O_LENGTH_B, length);

        state = State.DONE;
        return variableWidthSectionOffset;
    }

    public RowDataBuilder(RowDef rowDef, RowData rowData) {
        // TODO argument validations
        this.rowDef = rowDef;
        this.rowData = rowData;
        fixedWidthSectionOffset = rowData.getRowStart();
        state = State.NEWLY_CONSTRUCTED;
        this.fieldWidths = new int[rowDef.getFieldCount()];
        this.adapter = new NewValueAdapter();
    }

    private final ValueAdapter<?,?> adapter;
    private final RowDef rowDef;
    private final RowData rowData;
    private final int[] fieldWidths;
    private State state;
    private int fixedWidthSectionOffset;
    private int fieldIndex;
    private int nullMapOffset = -1;
    private int vmax;
    private int vlength;
    private int variableWidthSectionOffset;

    private void checkWithinRange(State requiredState) {
        state.require(requiredState);
        assert fieldIndex >= 0;
        if (fieldIndex >= rowDef.getFieldCount()) {
            throw new IllegalArgumentException("went past last field index of " + rowDef.getFieldCount());
        }
    }

    private static abstract class ValueAdapter<S,T extends RowDataTarget> {
        public abstract void doConvert(S source, T target, FieldDef fieldDef);
        public abstract void objectToSource(Object object, FieldDef fieldDef);
        protected abstract S nullSource(FieldDef fieldDef);
        public abstract boolean isNull(S source);

        public void convert(FieldDef fieldDef) {
            try {
                doConvert(source, target, fieldDef);
            } catch (ArrayIndexOutOfBoundsException e) {
                throw EncodingException.dueTo(e); // assumed to be during writing to the RowData's byte[]
            }
        }

        public S source() {
            return source;
        }

        public T target() {
            return target;
        }


        protected ValueAdapter(S source, T target) {
            this.source = source;
            this.target = target;
        }

        private S source;
        private T target;
    }

    private static class NewValueAdapter extends ValueAdapter<ValueSource,RowDataValueTarget> {

        @Override
        public void doConvert(ValueSource source, RowDataValueTarget target, FieldDef fieldDef) {
            TInstance type = target.targetType();
            if (stringInput != null) {
                // turn the string input into a ValueSource, then give it to TClass.fromObject.
                // Strings being inserted to binary types are a special, weird case.
                if (type.typeClass() instanceof MBinary) {
                    target.putStringBytes(stringInput);
                    return;
                }
                if (stringCache == null)
                    stringCache = new Value(MString.VARCHAR.instance(Integer.MAX_VALUE, true));
                stringCache.putString(stringInput, null);
                TExecutionContext context = new TExecutionContext(null, type, null);
                type.typeClass().fromObject(context, stringCache, value);
            }
            type.writeCanonical(value, target);
        }

        @Override
        public void objectToSource(Object object, FieldDef fieldDef) {
            TInstance underlying = underlying(fieldDef);
            value.underlying(underlying);
            stringInput = null;
            if (object == null) {
                ValueTargets.copyFrom(nullSource(fieldDef), value);
            }
            else if (object instanceof String) {
                // This is the common case, so let's test for it first
                if (TInstance.underlyingType(underlying) == UnderlyingType.STRING)
                    value.putString((String)object, null);
                else
                    stringInput = (String)object;
            }
            else {
                switch (TInstance.underlyingType(underlying)) {
                case INT_8:
                case INT_16:
                case UINT_16:
                case INT_32:
                case INT_64:
                    if (object instanceof Number)
                        ValueSources.valueFromLong(((Number) object).longValue(), value);
                    break;
                case FLOAT:
                    if (object instanceof Number)
                        value.putFloat(((Number)object).floatValue());
                    break;
                case DOUBLE:
                    if (object instanceof Number)
                        value.putDouble(((Number)object).doubleValue());
                    break;
                case BYTES:
                    if (object instanceof byte[])
                        value.putBytes((byte[])object);
                    else if (object instanceof ByteSource)
                        value.putBytes(((ByteSource)object).toByteSubarray());
                    break;
                case STRING:
                    value.putString(object.toString(), null);
                    break;
                case BOOL:
                    if (object instanceof Boolean)
                        value.putBool((Boolean)object);
                    break;
                }
                if (!value.hasAnyValue()) {
                    // last ditch effort! This is mostly to play nice with the loosey-goosy typing from types2.
                    ValueCacher cacher = fieldDef.column().getType().typeClass().cacher();
                    if (cacher != null)
                        object = cacher.sanitize(object);
                    value.putObject(object);
                }
            }
        }

        @Override
        public ValueSource nullSource(FieldDef fieldDef) {
            return ValueSources.getNullSource(underlying(fieldDef));
        }

        @Override
        public boolean isNull(ValueSource source) {
            return (stringInput == null) && source.isNull();
        }

        private TInstance underlying(FieldDef fieldDef) {
            return fieldDef.column().getType();
        }

        public NewValueAdapter() {
            this(
                    new Value(),
                    new RowDataValueTarget());
        }

        public NewValueAdapter(Value value, RowDataValueTarget target) {
            super(value,  target);
            this.value = value;
        }

        private String stringInput;
        private Value value;
        private Value stringCache;
    }

    private void nullRemainingAllocations() {
        int fieldsCount = rowDef.getFieldCount();
        while ( fieldIndex < fieldsCount) {
            allocate(rowDef.getFieldDef(fieldIndex), null);
        }
    }

    private <S> void nullRemainingPuts(ValueAdapter<S,?> adapter) {
        int fieldsCount = rowDef.getFieldCount();
        while ( fieldIndex < fieldsCount) {
            FieldDef fieldDef = rowDef.getFieldDef(fieldIndex);
            S source = adapter.nullSource(fieldDef);
            convert(source, adapter, fieldDef);
        }
    }

    private int readVarWidth(byte[] bytes, int widthWidth) {
        switch (widthWidth) {
            case 1: return AkServerUtil.getUByte(bytes, fixedWidthSectionOffset);
            case 2: return AkServerUtil.getUShort(bytes, fixedWidthSectionOffset);
            case 3: return AkServerUtil.getUMediumInt(bytes, fixedWidthSectionOffset);
            case 4: return AkServerUtil.getInt(bytes, fixedWidthSectionOffset);
            default: throw new UnsupportedOperationException("bad width-of-width: " + widthWidth);
        }
    }

    private enum State {
        NEWLY_CONSTRUCTED,
        ALLOCATING,
        PUTTING,
        DONE
        ;

        void require(State requiredState) {
            if (this != requiredState) {
                throw new IllegalStateException("required state " + requiredState + " but am currently " + name());
            }
        }
    }
}
TOP

Related Classes of com.foundationdb.server.rowdata.RowDataBuilder$ValueAdapter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.