io.pravega.test.integration.service.selftest.TruncateableArray.java Source code

Java tutorial

Introduction

Here is the source code for io.pravega.test.integration.service.selftest.TruncateableArray.java

Source

/**
 * Copyright (c) 2017 Dell Inc., or its subsidiaries. All Rights Reserved.
 *
 * 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
 */
package io.pravega.test.integration.service.selftest;

import io.pravega.common.io.StreamHelpers;
import io.pravega.common.util.ArrayView;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterators;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.io.UncheckedIOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import javax.annotation.concurrent.NotThreadSafe;

/**
 * Represents an ArrayView that can append at one end and truncate at the other one.
 */
@NotThreadSafe
class TruncateableArray implements ArrayView {
    //region Members

    /**
     * We copy the data in fixed sized blocks (of 1MB); this makes lookups a lot faster.
     */
    private static final int BLOCK_SIZE = 1024 * 1024;
    private final ArrayDeque<byte[]> arrays;
    private int firstArrayOffset;
    private int length;

    //endregion

    //region Constructor

    /**
     * Creates a new instance of the TruncateableArray class.
     */
    TruncateableArray() {
        this.arrays = new ArrayDeque<>();
        this.firstArrayOffset = 0;
        this.length = 0;
    }

    //endregion

    //region ArrayView Implementation

    @Override
    public byte get(int index) {
        Preconditions.checkArgument(index >= 0 && index < this.length,
                "index must be non-negative and less than the length of the array.");

        // Adjust the index based on the first entry offset.
        index += this.firstArrayOffset;

        // Find the array which contains the sought index.
        for (byte[] array : this.arrays) {
            if (index < array.length) {
                // This is the array we are looking for; use 'index' to look up into it.
                return array[index];
            }

            index -= array.length;
        }

        throw new AssertionError("unable to locate an inner array based on given input");
    }

    @Override
    public int getLength() {
        return this.length;
    }

    @Override
    public byte[] array() {
        throw new IllegalStateException("array() not supported.");
    }

    @Override
    public int arrayOffset() {
        throw new IllegalStateException("arrayOffset() not supported.");
    }

    @Override
    public InputStream getReader() {
        return getReader(0, this.length);
    }

    @Override
    public InputStream getReader(int offset, int length) {
        Preconditions.checkArgument(offset >= 0, "offset must be a non-negative number.");
        Preconditions.checkArgument(length >= 0, "length must be a non-negative number.");
        Preconditions.checkArgument(offset + length <= this.length,
                "offset+length must be non-negative and less than or equal to the length of the array.");
        ArrayList<ByteArrayInputStream> streams = new ArrayList<>();

        // Adjust the index based on the first entry offset.
        offset += this.firstArrayOffset;

        // Find the array which contains the starting offset.
        for (byte[] array : this.arrays) {
            if (offset >= array.length) {
                // Not interested in this array
                offset -= array.length;
                continue;
            }

            // Figure out how much of this array we need to extract.
            int arrayLength = Math.min(length, array.length - offset);
            streams.add(new ByteArrayInputStream(array, offset, arrayLength));
            offset = 0;

            // Reduce the requested length by the amount of data we copied.
            length -= arrayLength;
            if (length <= 0) {
                // We've reached the end.
                break;
            }
        }

        return new SequenceInputStream(Iterators.asEnumeration(streams.iterator()));
    }

    //endregion

    //region Operations

    /**
     * Appends the given InputStream at the end of the array.
     *
     * @param dataStream The InputStream to append.
     * @param length     The length of the InputStream to append.
     */
    void append(InputStream dataStream, int length) {
        if (length == 0) {
            return;
        }

        int insertOffset = (this.firstArrayOffset + this.length) % BLOCK_SIZE;
        while (length > 0) {
            byte[] insertArray;
            if (insertOffset == 0) {
                // Last Array full
                insertArray = new byte[BLOCK_SIZE];
                this.arrays.add(insertArray);
            } else {
                insertArray = this.arrays.getLast();
            }

            int toCopy = Math.min(length, BLOCK_SIZE - insertOffset);
            try {
                int bytesCopied = StreamHelpers.readAll(dataStream, insertArray, insertOffset, toCopy);
                assert bytesCopied == toCopy : "unable to read the requested number of bytes";
            } catch (IOException ex) {
                throw new UncheckedIOException(ex);
            }

            length -= toCopy;
            this.length += toCopy;
            insertOffset = 0;
        }
    }

    /**
     * Truncates a number of bytes from the beginning of the array.
     *
     * @param truncationLength The number of bytes to truncate.
     */
    void truncate(int truncationLength) {
        Preconditions.checkPositionIndex(truncationLength, this.length,
                "trimLength must be non-negative and less than the length of the array.");
        this.length -= truncationLength;

        while (this.arrays.size() > 0 && truncationLength > 0) {
            byte[] first = this.arrays.getFirst();
            if (truncationLength >= first.length - this.firstArrayOffset) {
                // We need to truncate more than what is available in the first array; chop it all off.
                this.arrays.removeFirst();
                truncationLength -= first.length - this.firstArrayOffset;
                this.firstArrayOffset = 0;
            } else {
                // We need to truncate less than what is available in the first array; adjust offset.
                this.firstArrayOffset += truncationLength;
                truncationLength = 0;
            }
        }

        assert truncationLength == 0 : "not all requested bytes were truncated";
        if (this.arrays.size() == 0) {
            assert this.firstArrayOffset == 0 : "first entry offset not reset when no entries exist";
            assert this.length == 0 : "non-zero length when no entries exist";
        }
    }

    //endregion
}