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

org.cp.elements.nio.ByteBufferInputStream Maven / Gradle / Ivy

/*
 * Copyright 2011-Present Author or Authors.
 *
 * 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.cp.elements.nio;

import static org.cp.elements.lang.LangExtensions.assertThat;
import static org.cp.elements.lang.RuntimeExceptionsFactory.newIndexOutOfBoundsException;
import static org.cp.elements.lang.RuntimeExceptionsFactory.newNullPointerException;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;

import org.cp.elements.lang.Assert;
import org.cp.elements.lang.ObjectUtils;
import org.cp.elements.lang.annotation.NotNull;

/**
 * An {@link InputStream} implementation that reads from a Java {@link ByteBuffer}.
 *
 * @author John Blum
 * @see java.io.InputStream
 * @see java.nio.ByteBuffer
 * @since 1.0.0
 */
@SuppressWarnings("unused")
public class ByteBufferInputStream extends InputStream {

  /**
   * Factory method used to construct a new instance of {@link ByteBufferInputStream} backed by
   * the given, required {@link ByteBuffer}.
   *
   * @param byteBuffer {@link ByteBuffer} used to back the {@link InputStream}; must not be {@literal null}.
   * @return a new {@link ByteBufferInputStream} initialized with the given, required {@link ByteBuffer}.
   * @throws IllegalArgumentException if the {@link ByteBuffer} is {@literal null}.
   * @see #ByteBufferInputStream(ByteBuffer)
   * @see java.nio.ByteBuffer
   */
  public static @NotNull ByteBufferInputStream from(@NotNull ByteBuffer byteBuffer) {
    return new ByteBufferInputStream(byteBuffer);
  }

  private final ByteBuffer byteBuffer;

  /**
   * Constructs a new instance of {@link ByteBufferInputStream} backed by the given, required {@link ByteBuffer}.
   *
   * @param byteBuffer {@link ByteBuffer} used to back the {@link InputStream}; must not be {@literal null}.
   * @throws IllegalArgumentException if the {@link ByteBuffer} is {@literal null}.
   * @see java.nio.ByteBuffer
   */
  public ByteBufferInputStream(@NotNull ByteBuffer byteBuffer) {
    this.byteBuffer = prepareForReading(ObjectUtils.requireObject(byteBuffer, "ByteBuffer is required"));
  }

  private @NotNull ByteBuffer prepareForReading(@NotNull ByteBuffer byteBuffer) {

    return (ByteBuffer) (byteBuffer.position() > 0
      ? byteBuffer.limit(byteBuffer.position()).rewind().mark()
      : byteBuffer.limit(byteBuffer.capacity())).mark();
  }

  /**
   * Gets the {@link ByteBuffer} used to back this {@link InputStream}.
   *
   * @return the {@link ByteBuffer} used to back this {@link InputStream}.
   * @see java.nio.ByteBuffer
   */
  protected @NotNull ByteBuffer getByteBuffer() {
    return this.byteBuffer;
  }

  /**
   * Determines the number of bytes still available to be read from the backing {@link ByteBuffer}
   * using this {@link InputStream}.
   *
   * @return the number of bytes still available to be read.
   * @see java.nio.ByteBuffer#remaining()
   */
  @Override
  public int available() {
    return getByteBuffer().remaining();
  }

  /**
   * Marks this {@link InputStream} to the current {@link ByteBuffer#position()}.
   *
   * @param readLimit ignored. This {@link InputStream} is backed by a {@link ByteBuffer} and therefore can recall
   * any number of bytes read before {@link #reset()} is called and the {@code readLimit} is reached.
   * @see java.nio.ByteBuffer#mark()
   */
  @Override
  public synchronized void mark(int readLimit) {
    getByteBuffer().mark();
  }

  /**
   * Return {@literal true} to indicate {@link #mark(int)} is supported.
   *
   * @return {@literal true}.
   */
  @Override
  public boolean markSupported() {
    return true;
  }

  /**
   * Read a single byte from the {@link ByteBuffer} using this {@link InputStream}.
   *
   * @return an {@link Integer} value containing the byte read from the backing {@link ByteBuffer}.
   * @throws IOException if an error occurs reading from the backing {@link ByteBuffer}.
   */
  @Override
  public int read() throws IOException {
    return available() > 0 ? getByteBuffer().get() : -1;
  }

  /**
   * Reads at most {@code length} bytes from the backing {@link ByteBuffer} using this {@link InputStream}
   * into the given, required byte array starting at the given offset.
   *
   * @param buffer byte array written from the bytes read from this {@link InputStream}; must not be {@literal null}.
   * @param offset {@link Integer} referring to the index in the byte array to start writing bytes.
   * @param length {@link Integer} specifying the number of bytes to write into the byte array
   * from this {@link InputStream}.
   * @return an {@link Integer} indicating the number of bytes read from this {@link InputStream} into the byte array.
   * Returns {@literal 0} if the length of the byte array is {@literal 0}. Returns {@literal -1} if there are no
   * {@link #available()} bytes in this {@link InputStream}.
   * @throws NullPointerException if the byte array is {@literal null}.
   * @throws IndexOutOfBoundsException if {@code offset} is less than {@literal 0} or greater than equal to
   * the byte array length, or the length is less than {@literal 0} or greater than
   * the byte array length - {@code offset}.
   */
  @Override
  public int read(@NotNull byte[] buffer, int offset, int length) throws IOException {

    Assert.notNull(buffer, newNullPointerException("Byte array cannot be null"));

    if (buffer.length > 0) {

      String offsetExceptionMessage = "Offset [%d] must be greater than equal to 0 and less than [%d]";

      assertThat(offset)
        .throwing(newIndexOutOfBoundsException(offsetExceptionMessage, offset, buffer.length))
        .isGreaterThanEqualToAndLessThan(0, buffer.length);

      String lengthExceptionMessage = "Length [%d] must be greater than equal to 0 and less than equal to [%d]";

      assertThat(length)
        .throwing(newIndexOutOfBoundsException(lengthExceptionMessage, length, buffer.length - offset))
        .isGreaterThanEqualToAndLessThanEqualTo(0, buffer.length - offset);

      int numberOfBytesToRead = Math.min(available(), length);

      if (numberOfBytesToRead > 0) {
        getByteBuffer().get(buffer, offset, numberOfBytesToRead);
        return numberOfBytesToRead;
      }

      return -1;
    }

    return 0;
  }

  /**
   * Resets the underlying, backing {@link ByteBuffer} to the last {@link ByteBuffer#mark() marked position}.
   *
   * @see java.nio.ByteBuffer#reset()
   */
  @Override
  public synchronized void reset() {
    getByteBuffer().reset();
  }

  /**
   * Skips the given number of bytes read from this {@link InputStream}.
   *
   * Effectively moves the {@link ByteBuffer#position()} ahead by the number of available or given number of bytes,
   * which ever is smaller.
   *
   * @param numberOfBytes {@link Long} specifying the number of bytes to skip in this {@link InputStream}.
   * @return an {@link Integer} indicating the actual number of bytes skipped.
   * @see java.nio.ByteBuffer#position(int)
   * @see #available()
   */
  @Override
  public long skip(long numberOfBytes) {

    if (numberOfBytes > 0) {
      int actualNumberOfBytes = (int) Math.min(available(), numberOfBytes);
      ByteBuffer byteBuffer = getByteBuffer();
      byteBuffer.position(byteBuffer.position() + actualNumberOfBytes);
      return actualNumberOfBytes;
    }

    return 0;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy