com.foundationdb.sql.server.ServerValueEncoder.java Source code

Java tutorial

Introduction

Here is the source code for com.foundationdb.sql.server.ServerValueEncoder.java

Source

/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2009-2015 FoundationDB, LLC
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.foundationdb.sql.server;

import com.foundationdb.server.error.InvalidParameterValueException;
import com.foundationdb.server.error.UnsupportedCharsetException;
import com.foundationdb.server.error.ZeroDateTimeException;
import com.foundationdb.server.types.FormatOptions;
import com.foundationdb.server.types.TExecutionContext;
import com.foundationdb.server.types.TInstance;
import com.foundationdb.server.types.aksql.aktypes.AkGUID;
import com.foundationdb.server.types.common.types.TString;
import com.foundationdb.server.types.common.types.TypesTranslator;
import com.foundationdb.server.types.mcompat.mtypes.MDateAndTime;
import com.foundationdb.server.types.value.Value;
import com.foundationdb.server.types.value.ValueSource;
import com.foundationdb.server.types.value.ValueSources;
import com.foundationdb.util.AkibanAppender;

import com.foundationdb.util.Strings;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import java.math.BigDecimal;
import java.sql.Types;
import java.util.Collections;
import java.util.Date;
import java.io.*;

/** Encode result values for transmission. */
public class ServerValueEncoder {

    public static enum ZeroDateTimeBehavior {
        NONE(null), EXCEPTION("exception"), ROUND("round"), CONVERT_TO_NULL("convertToNull");

        private String propertyName;

        ZeroDateTimeBehavior(String propertyName) {
            this.propertyName = propertyName;
        }

        public static ZeroDateTimeBehavior fromProperty(String name) {
            if (name == null)
                return NONE;
            for (ZeroDateTimeBehavior zdtb : values()) {
                if (name.equals(zdtb.propertyName))
                    return zdtb;
            }
            throw new InvalidParameterValueException(
                    String.format("Invalid name: %s for ZeroDateTimeBehavior", name));
        }
    }

    public static final ValueSource ROUND_ZERO_DATETIME_SOURCE = new Value(MDateAndTime.DATETIME.instance(false),
            MDateAndTime.encodeDateTime(1, 1, 1, 0, 0, 0));
    public static final ValueSource ROUND_ZERO_DATE_SOURCE = new Value(MDateAndTime.DATE.instance(false),
            MDateAndTime.encodeDate(1, 1, 1));

    private final TypesTranslator typesTranslator;
    private final String encoding;
    private ZeroDateTimeBehavior zeroDateTimeBehavior;
    private FormatOptions options;
    private final ByteArrayOutputStream byteStream;
    private final PrintWriter printWriter;
    private final AkibanAppender appender;
    private DataOutputStream dataStream;

    public ServerValueEncoder(TypesTranslator typesTranslator, String encoding, FormatOptions options) {
        this(typesTranslator, encoding, new ByteArrayOutputStream(), options);
    }

    public ServerValueEncoder(TypesTranslator typesTranslator, String encoding,
            ZeroDateTimeBehavior zeroDateTimeBehavior, FormatOptions options) {
        this(typesTranslator, encoding, options);
        this.zeroDateTimeBehavior = zeroDateTimeBehavior;
    }

    public ServerValueEncoder(TypesTranslator typesTranslator, String encoding, ByteArrayOutputStream byteStream,
            FormatOptions options) {
        this.typesTranslator = typesTranslator;
        this.encoding = encoding;
        this.byteStream = byteStream;
        this.options = options;
        try {
            printWriter = new PrintWriter(new OutputStreamWriter(byteStream, encoding));
        } catch (UnsupportedEncodingException ex) {
            throw new UnsupportedCharsetException(encoding);
        }
        // If the target encoding is UTF-8, we can support
        // canAppendBytes() for properly encoded source strings.
        if ("UTF-8".equals(encoding))
            appender = AkibanAppender.of(byteStream, printWriter, "UTF-8");
        else
            appender = AkibanAppender.of(printWriter);
    }

    public String getEncoding() {
        return encoding;
    }

    public ByteArrayOutputStream getByteStream() {
        printWriter.flush();
        return byteStream;
    }

    public AkibanAppender getAppender() {
        return appender;
    }

    public DataOutputStream getDataStream() {
        printWriter.flush();
        if (dataStream == null)
            dataStream = new DataOutputStream(byteStream);
        return dataStream;
    }

    /**
     * Encode the given value into a stream that can then be passed
     * to
     * <code>writeByteStream</code>.
     */
    public ByteArrayOutputStream encodeValue(ValueSource value, ServerType type, boolean binary)
            throws IOException {
        if (value.isNull())
            return null;
        if ((zeroDateTimeBehavior != ZeroDateTimeBehavior.NONE)
                && (((type.getType().typeClass() == MDateAndTime.DATE) && (value.getInt32() == 0))
                        || ((type.getType().typeClass() == MDateAndTime.DATETIME) && (value.getInt64() == 0)))) {
            switch (zeroDateTimeBehavior) {
            case EXCEPTION:
                throw new ZeroDateTimeException();
            case ROUND:
                value = (type.getType().typeClass() == MDateAndTime.DATETIME) ? ROUND_ZERO_DATETIME_SOURCE
                        : ROUND_ZERO_DATE_SOURCE;
                break;
            case CONVERT_TO_NULL:
                return null;
            }
        }
        reset();
        appendValue(value, type, binary);
        return getByteStream();
    }

    /** Encode the given direct value. */
    public ByteArrayOutputStream encodePObject(Object value, ServerType type, boolean binary) throws IOException {
        if (value == null)
            return null;
        reset();
        appendPObject(value, type, binary);
        return getByteStream();
    }

    /** Reset the contents of the buffer. */
    public void reset() {
        getByteStream().reset();
    }

    /** Append the given value to the buffer. */
    public void appendValue(ValueSource value, ServerType type, boolean binary) throws IOException {
        if (!binary) {
            // Handle unusual text encoding of binary types.
            switch (type.getBinaryEncoding()) {
            case BINARY_OCTAL_TEXT:
                processBinaryText(value);
                break;

            default:
                type.getType().format(value, appender);
                break;
            }
        } else {
            switch (type.getBinaryEncoding()) {
            case BINARY_OCTAL_TEXT:
                getByteStream().write(value.getBytes());
                break;
            case INT_8:
                getDataStream().write((byte) typesTranslator.getIntegerValue(value));
                break;
            case INT_16:
                getDataStream().writeShort((short) typesTranslator.getIntegerValue(value));
                break;
            case INT_32:
                getDataStream().writeInt((int) typesTranslator.getIntegerValue(value));
                break;
            case INT_64:
                getDataStream().writeLong(typesTranslator.getIntegerValue(value));
                break;
            case FLOAT_32:
                getDataStream().writeFloat(value.getFloat());
                break;
            case FLOAT_64:
                getDataStream().writeDouble(value.getDouble());
                break;
            case STRING_BYTES:
                getByteStream().write(value.getString().getBytes(encoding));
                break;
            case BOOLEAN_C:
                getDataStream().write(value.getBoolean() ? 1 : 0);
                break;
            case TIMESTAMP_FLOAT64_SECS_2000_NOTZ:
                getDataStream().writeDouble(seconds2000NoTZ(typesTranslator.getTimestampMillisValue(value))
                        + typesTranslator.getTimestampNanosValue(value) / 1.0e9);
                break;
            case TIMESTAMP_INT64_MICROS_2000_NOTZ:
                getDataStream().writeLong(seconds2000NoTZ(typesTranslator.getTimestampMillisValue(value)) * 1000000L
                        + typesTranslator.getTimestampNanosValue(value) / 1000);
                break;
            case DAYS_2000:
                getDataStream().writeInt(days2000(typesTranslator.getTimestampMillisValue(value)));
                break;
            case TIME_FLOAT64_SECS_NOTZ:
                getDataStream().writeDouble(timeSecsNoTZ(typesTranslator.getTimestampMillisValue(value)));
                break;
            case TIME_INT64_MICROS_NOTZ:
                getDataStream().writeLong(timeSecsNoTZ(typesTranslator.getTimestampMillisValue(value)) * 1000000L);
                break;
            case DECIMAL_PG_NUMERIC_VAR:
                for (short d : pgNumericVar(typesTranslator.getDecimalValue(value))) {
                    getDataStream().writeShort(d);
                }
                break;
            case UUID:
                getDataStream().write(AkGUID.uuidToBytes((java.util.UUID) value.getObject()));
                break;
            case NONE:
            default:
                throw new UnsupportedOperationException("No binary encoding for " + type);
            }
        }
    }

    private void processBinaryText(ValueSource value) {
        FormatOptions.BinaryFormatOption bfo = options.get(FormatOptions.BinaryFormatOption.class);
        String formattedString = bfo.format(value.getBytes());
        printWriter.append(formattedString);
    }

    /** Append the given direct object to the buffer. */
    public void appendPObject(Object value, ServerType type, boolean binary) throws IOException {
        if (type.getType().typeClass() instanceof TString && value instanceof String) {
            // Optimize the common case of directly encoding a string.
            printWriter.write((String) value);
            return;
        }

        ValueSource source = valuefromObject(value, type);
        appendValue(source, type, binary);
    }

    public ValueSource valuefromObject(Object value, ServerType type) {
        if (value instanceof Date) {
            TInstance dateType = javaDateTInstance(value);
            Value dateValue = new Value(dateType);
            typesTranslator.setTimestampMillisValue(dateValue, ((Date) value).getTime(),
                    (value instanceof java.sql.Timestamp) ? ((java.sql.Timestamp) value).getNanos() : 0);
            TInstance targetType = type.getType();
            if (dateType.equals(targetType))
                return dateValue;
            TExecutionContext context = new TExecutionContext(Collections.singletonList(dateType), targetType,
                    null);
            Value result = new Value(targetType);
            targetType.typeClass().fromObject(context, dateValue, result);
            return result;
        } else {
            // TODO this is inefficient, but I want to get it working.
            return ValueSources.valuefromObject(value, type.getType());
        }
    }

    private TInstance javaDateTInstance(Object value) {
        int jdbcType;
        if (value instanceof java.sql.Date) {
            jdbcType = Types.DATE;
        } else if (value instanceof java.sql.Time) {
            jdbcType = Types.TIME;
        } else {
            jdbcType = Types.TIMESTAMP;
        }
        return typesTranslator.typeClassForJDBCType(jdbcType).instance(true);
    }

    public void appendString(String string) throws IOException {
        printWriter.write(string);
    }

    public PrintWriter getWriter() {
        return printWriter;
    }

    /** Adjust milliseconds since 1970-01-01 00:00:00-UTC to seconds since
     * 2000-01-01 00:00:00 timezoneless. A conversion from local time
     * to UTC involves an offset that varies for Summer time. A
     * conversion from local time to timezoneless just removes the
     * zone as though all days were the same length.
     */
    private static long seconds2000NoTZ(long millis) {
        DateTimeZone dtz = DateTimeZone.getDefault();
        millis += dtz.getOffset(millis);
        return millis / 1000 - 946684800; // 2000-01-01 00:00:00-UTC.
    }

    public static int days2000(long millis) {
        long secs = seconds2000NoTZ(millis);
        return (int) (secs / 86400);
    }

    public static int timeSecsNoTZ(long millis) {
        DateTime dt = new DateTime(millis);
        return dt.getSecondOfDay();
    }

    private static final short NUMERIC_POS = 0x0000;
    private static final short NUMERIC_NEG = 0x4000;
    private static final short NUMERIC_NAN = (short) 0xC000;

    private static short[] pgNumericVar(BigDecimal n) {
        short ndigits, weight, sign, dscale;
        dscale = (short) n.scale();
        if (dscale < 0)
            dscale = 0;
        String s = n.toPlainString();
        int lpos = 0;
        sign = NUMERIC_POS;
        if (s.charAt(lpos) == '-') {
            sign = NUMERIC_NEG;
            lpos++;
        }
        int dposl = s.indexOf('.', lpos), dposr;
        if (dposl < 0)
            dposr = dposl = s.length();
        else
            dposr = dposl + 1;
        int nleft = (dposl - lpos + 3) / 4;
        weight = (short) (nleft - 1);
        int nright = (s.length() - dposr + 3) / 4;
        ndigits = (short) (nleft + nright);
        while ((ndigits > 0) && (pgNumericDigit(s, ndigits - 1, lpos, dposl, dposr, nleft, nright) == 0)) {
            ndigits--;
        }
        short[] digits = new short[ndigits + 4];
        digits[0] = ndigits;
        digits[1] = weight;
        digits[2] = sign;
        digits[3] = dscale;
        for (int i = 0; i < ndigits; i++) {
            digits[i + 4] = pgNumericDigit(s, i, lpos, dposl, dposr, nleft, nright);
        }
        return digits;
    }

    private static short pgNumericDigit(String s, int index, int lpos, int dposl, int dposr, int nleft,
            int nright) {
        short result = 0;
        if (index < nleft) {
            int pos = dposl + (index - nleft) * 4;
            for (int i = 0; i < 4; i++) {
                result = (short) (result * 10);
                if (pos >= lpos)
                    result += s.charAt(pos) - '0';
                pos++;
            }
        } else {
            int pos = dposr + (index - nleft) * 4;
            for (int i = 0; i < 4; i++) {
                result = (short) (result * 10);
                if (pos < s.length())
                    result += s.charAt(pos) - '0';
                pos++;
            }
        }
        return result;
    }

}