co.cask.tigon.sql.io.GDATEncoder.java Source code

Java tutorial

Introduction

Here is the source code for co.cask.tigon.sql.io.GDATEncoder.java

Source

/*
 * Copyright  2014 Cask Data, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package co.cask.tigon.sql.io;

import co.cask.tigon.io.Encoder;
import co.cask.tigon.sql.flowlet.GDATRecordType;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.primitives.Doubles;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.List;
import java.util.Map;

/**
 * A {@link Encoder} that performs all writes to an in memory buffer.
 */
public class GDATEncoder implements Encoder {
    private static final ByteOrder byteOrder = ByteOrder.LITTLE_ENDIAN;
    private final OutStream buffer;
    // Location, String
    private final List<Map.Entry<Integer, byte[]>> stringOffsetLocations = Lists.newArrayList();
    //Size of String Payload
    private int stringPayloadSize = 0;

    /** ByteBuffer objects shared by all functions in this object **/
    private final ByteBuffer sharedByteBuffer;

    /**
     * GDATEncoder Constructor
     */
    public GDATEncoder() {
        buffer = new OutStream();
        sharedByteBuffer = ByteBuffer.allocate(8).order(byteOrder);
    }

    /**
     * Customized child class of ByteArrayOutputStream that allows direct access to the underlying byte array.
     * [Created to avoid duplicating byte arrays]
     */
    private static final class OutStream extends ByteArrayOutputStream {

        /**
         * @return reference to the underlying byte array
         */
        public byte[] getByteArray() {
            return buf;
        }
    }

    /**
     * Write Length in Big Endian Order as per GDAT format specification.
     */
    private void writeLengthToStream(int length, OutputStream out) throws IOException {
        sharedByteBuffer.clear();
        sharedByteBuffer.order(ByteOrder.BIG_ENDIAN).putInt(length);
        sharedByteBuffer.flip();
        out.write(sharedByteBuffer.array(), 0, Ints.BYTES);
        sharedByteBuffer.order(byteOrder);
    }

    private void writeIntToStream(int val, OutputStream out) throws IOException {
        sharedByteBuffer.clear();
        sharedByteBuffer.putInt(val);
        sharedByteBuffer.flip();
        out.write(sharedByteBuffer.array(), 0, Ints.BYTES);
    }

    @Override
    public Encoder writeNull() throws IOException {
        return this;
    }

    @Override
    public Encoder writeBool(boolean b) throws IOException {
        return writeInt(b ? 1 : 0);
    }

    @Override
    public Encoder writeInt(int i) throws IOException {
        //Get all four Bytes of Signed Integer.
        writeIntToStream(i, buffer);
        return this;
    }

    @Override
    public Encoder writeLong(long l) throws IOException {
        sharedByteBuffer.clear();
        sharedByteBuffer.putLong(l);
        sharedByteBuffer.flip();
        buffer.write(sharedByteBuffer.array(), 0, Longs.BYTES);
        return this;
    }

    @Override
    public Encoder writeFloat(float v) throws IOException {
        //GDAT format will only contain double data type values and will not contain any float type values
        throw new UnsupportedOperationException("Does not support Float type objects, use Double");
    }

    @Override
    public Encoder writeDouble(double v) throws IOException {
        //Get all 8 Bytes of a Double.
        sharedByteBuffer.clear();
        sharedByteBuffer.putDouble(v);
        sharedByteBuffer.flip();
        buffer.write(sharedByteBuffer.array(), 0, Doubles.BYTES);
        return this;
    }

    @Override
    public Encoder writeString(String s) throws IOException {
        // String data to be inserted at location -> buffer.size()
        byte[] stringBytes = s.getBytes(Charsets.UTF_8);
        stringPayloadSize += stringBytes.length;
        stringOffsetLocations.add(Maps.immutableEntry(buffer.size(), stringBytes));
        return this;
    }

    @Override
    public Encoder writeBytes(byte[] bytes) throws IOException {
        throw new UnsupportedOperationException("Does not support reading/writing Bytes");
    }

    @Override
    public Encoder writeBytes(byte[] bytes, int i, int i2) throws IOException {
        throw new UnsupportedOperationException("Does not support reading/writing Bytes");
    }

    @Override
    public Encoder writeBytes(ByteBuffer byteBuffer) throws IOException {
        throw new UnsupportedOperationException("Does not support reading/writing Bytes");
    }

    private void writeToRecord(OutputStream outputStream, byte recordMarker) throws IOException {
        buffer.write(recordMarker);
        // Maintains the location of the next byte to be written to the outputStream
        int outputArrayCounter = 0;
        int offsetPointer = buffer.size() + stringOffsetLocations.size() * 3 * Ints.BYTES;
        byte[] bufferByteArray = buffer.getByteArray();

        //4 Bytes unsigned int to represent length of the data record.
        writeLengthToStream(offsetPointer + stringPayloadSize, outputStream);

        for (Map.Entry<Integer, byte[]> stringEntry : stringOffsetLocations) {
            // Bytes preceding location of string and after last outputArrayCounter
            outputStream.write(bufferByteArray, outputArrayCounter, stringEntry.getKey() - outputArrayCounter);
            byte[] stringBytes = stringEntry.getValue();
            // Write length of string
            writeIntToStream(stringBytes.length, outputStream);
            // Write offset of string
            writeIntToStream(offsetPointer, outputStream);
            // Write reserved
            writeIntToStream(0, outputStream);
            outputArrayCounter = stringEntry.getKey();
            offsetPointer += stringBytes.length;
        }
        // Write the remaining bytes from buffer
        outputStream.write(bufferByteArray, outputArrayCounter, buffer.size() - outputArrayCounter);
        // Write String values at the end of the record
        for (Map.Entry<Integer, byte[]> entry : stringOffsetLocations) {
            outputStream.write(entry.getValue());
        }
        buffer.reset();
    }

    /**
     * Writes a GDAT format DATA record to the provided outputStream. This method is to be called after calling all the
     * required write*() methods.
     *
     * @param outputStream GDAT record is written to this outputStream
     * @throws IOException
     */
    public void writeTo(OutputStream outputStream) throws IOException {
        writeToRecord(outputStream, GDATRecordType.DATA.getRecordMarker());
    }

    /**
     * Writes a GDAT format EOF record to the provided outputStream. This method is to be called after calling all the
     * required write*() methods.
     *
     * @param outputStream GDAT record is written to this outputStream
     * @throws IOException
     */
    public void writeEOFRecord(OutputStream outputStream) throws IOException {
        writeToRecord(outputStream, GDATRecordType.EOF.getRecordMarker());
    }
}