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

org.conqat.lib.commons.io.ChunkInputStream Maven / Gradle / Ivy

There is a newer version: 2024.7.2
Show newest version
/*
 * Copyright (c) CQSE GmbH
 *
 * 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 org.conqat.lib.commons.io;

import java.io.InputStream;
import java.util.List;

import org.conqat.lib.commons.assertion.CCSMAssert;
import org.conqat.lib.commons.collections.CollectionUtils;

/**
 * A stream that provides functionality for reading from a list of byte arrays, also called chunks.
 */
public class ChunkInputStream extends InputStream {

	/** The internal chunks to read from. */
	private final List chunks;

	/** The chunk's index that is to be read after the current chunk. */
	private int nextChunkIndex;

	/** The remaining number of bytes to read. */
	private int remaining;

	/** The chunk from which is currently read. */
	private byte[] currentChunk;

	/** The offset in the current chunk to read from. */
	private int currentOffset;

	/**
	 * Creates a new ChunkInputStream with the given chunks.
	 */
	public ChunkInputStream(List chunks) {
		this(chunks, getLastChunkSize(chunks));
	}

	/**
	 * Retrieves the last chunk's size or zero if the given chunk list is empty.
	 * 
	 * @param chunks
	 *            the chunks
	 * @return the last chunk's size
	 */
	private static int getLastChunkSize(List chunks) {
		CCSMAssert.isNotNull(chunks);
		if (chunks.isEmpty()) {
			return 0;
		}
		return CollectionUtils.getLast(chunks).length;
	}

	/**
	 * Creates a new ChunkInputStream with the given chunks. If the input chunks are the output of a
	 * ChunkOutputStream, the last chunk is probably smaller than its actual byte array size. Therefore
	 * the lastChunkSize can be specified additionally.
	 * 
	 * @param chunks
	 *            the chunks to read from, the list must not contain null values or empty arrays
	 * @param lastChunkSize
	 *            the real size of the last chunk
	 */
	public ChunkInputStream(List chunks, int lastChunkSize) {
		CCSMAssert.isNotNull(chunks);

		this.chunks = chunks;
		this.currentChunk = null;

		for (byte[] chunk : this.chunks) {
			remaining += chunk.length;
		}

		if (!this.chunks.isEmpty()) {
			this.currentChunk = this.chunks.get(0);
			byte[] lastChunk = CollectionUtils.getLast(this.chunks);
			CCSMAssert.isTrue(lastChunkSize > 0 && lastChunkSize <= lastChunk.length,
					"lastChunkSize must be in range ]0,lastChunk.length]");

			remaining -= lastChunk.length - lastChunkSize;
		}

		nextChunkIndex = 1;
		currentOffset = 0;
	}

	/** {@inheritDoc} */
	@Override
	public int read() {
		updateChunk();
		if (remaining <= 0) {
			return -1;
		}

		remaining--;
		return ByteArrayUtils.unsignedByte(currentChunk[currentOffset++]);
	}

	/** {@inheritDoc} */
	@Override
	public int read(byte[] b, int offset, int length) {
		CCSMAssert.isNotNull(b);
		CCSMAssert.isFalse(offset < 0 || length < 0 || length > b.length - offset, "invalid offset/length");

		if (length == 0) {
			return 0;
		}

		if (remaining <= 0) {
			return -1;
		}

		int alreadyRead = 0;
		while (remaining > 0 && alreadyRead < length) {
			updateChunk();

			// calculate how many bytes must/can be read from the current chunk
			int readNow = Math.min(currentChunk.length - currentOffset, length - alreadyRead);

			System.arraycopy(currentChunk, currentOffset, b, offset + alreadyRead, readNow);

			currentOffset += readNow;
			alreadyRead += readNow;
			remaining -= readNow;
		}
		return alreadyRead;
	}

	/** {@inheritDoc} */
	@Override
	public int available() {
		return remaining;
	}

	/** {@inheritDoc} */
	@Override
	public void close() {
		// do nothing
	}

	/**
	 * Updates the current chunk. If the current offset is at the end of the current chunk, switch to
	 * the next one. Sets the current chunk to null if there is no next chunk.
	 */
	private void updateChunk() {
		if (currentChunk != null && currentOffset == currentChunk.length) {
			if (nextChunkIndex < chunks.size()) {
				currentChunk = chunks.get(nextChunkIndex++);
				currentOffset = 0;
			} else {
				currentChunk = null;
			}
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy