UnsynchronizedByteArrayInputStream.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.io.input;

  18. import java.io.ByteArrayInputStream;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.util.Objects;

  22. import org.apache.commons.io.build.AbstractOrigin;
  23. import org.apache.commons.io.build.AbstractStreamBuilder;

  24. /**
  25.  * This is an alternative to {@link ByteArrayInputStream} which removes the synchronization overhead for non-concurrent access; as such this class is
  26.  * not thread-safe.
  27.  * <p>
  28.  * To build an instance, use {@link Builder}.
  29.  * </p>
  30.  *
  31.  * @see Builder
  32.  * @see ByteArrayInputStream
  33.  * @since 2.7
  34.  */
  35. //@NotThreadSafe
  36. public class UnsynchronizedByteArrayInputStream extends InputStream {

  37.     // @formatter:off
  38.     /**
  39.      * Builds a new {@link UnsynchronizedByteArrayInputStream}.
  40.      *
  41.      * <p>
  42.      * Using a Byte Array:
  43.      * </p>
  44.      * <pre>{@code
  45.      * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder()
  46.      *   .setByteArray(byteArray)
  47.      *   .setOffset(0)
  48.      *   .setLength(byteArray.length)
  49.      *   .get();
  50.      * }
  51.      * </pre>
  52.      * <p>
  53.      * Using File IO:
  54.      * </p>
  55.      * <pre>{@code
  56.      * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder()
  57.      *   .setFile(file)
  58.      *   .setOffset(0)
  59.      *   .setLength(byteArray.length)
  60.      *   .get();
  61.      * }
  62.      * </pre>
  63.      * <p>
  64.      * Using NIO Path:
  65.      * </p>
  66.      * <pre>{@code
  67.      * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder()
  68.      *   .setPath(path)
  69.      *   .setOffset(0)
  70.      *   .setLength(byteArray.length)
  71.      *   .get();
  72.      * }
  73.      * </pre>
  74.      *
  75.      * @see #get()
  76.      */
  77.     // @formatter:on
  78.     public static class Builder extends AbstractStreamBuilder<UnsynchronizedByteArrayInputStream, Builder> {

  79.         private int offset;
  80.         private int length;

  81.         /**
  82.          * Constructs a builder of {@link UnsynchronizedByteArrayInputStream}.
  83.          */
  84.         public Builder() {
  85.             // empty
  86.         }

  87.         /**
  88.          * Builds a new {@link UnsynchronizedByteArrayInputStream}.
  89.          * <p>
  90.          * You must set an aspect that supports {@code byte[]} on this builder, otherwise, this method throws an exception.
  91.          * </p>
  92.          * <p>
  93.          * This builder uses the following aspects:
  94.          * </p>
  95.          * <ul>
  96.          * <li>{@code byte[]}</li>
  97.          * <li>offset</li>
  98.          * <li>length</li>
  99.          * </ul>
  100.          *
  101.          * @return a new instance.
  102.          * @throws UnsupportedOperationException if the origin cannot provide a {@code byte[]}.
  103.          * @throws IllegalStateException         if the {@code origin} is {@code null}.
  104.          * @throws IOException                   if an I/O error occurs converting to an {@code byte[]} using {@link AbstractOrigin#getByteArray()}.
  105.          * @see AbstractOrigin#getByteArray()
  106.          * @see #getUnchecked()
  107.          */
  108.         @Override
  109.         public UnsynchronizedByteArrayInputStream get() throws IOException {
  110.             return new UnsynchronizedByteArrayInputStream(checkOrigin().getByteArray(), offset, length);
  111.         }

  112.         @Override
  113.         public Builder setByteArray(final byte[] origin) {
  114.             length = Objects.requireNonNull(origin, "origin").length;
  115.             return super.setByteArray(origin);
  116.         }

  117.         /**
  118.          * Sets the length.
  119.          *
  120.          * @param length Must be greater or equal to 0.
  121.          * @return {@code this} instance.
  122.          */
  123.         public Builder setLength(final int length) {
  124.             if (length < 0) {
  125.                 throw new IllegalArgumentException("length cannot be negative");
  126.             }
  127.             this.length = length;
  128.             return this;
  129.         }

  130.         /**
  131.          * Sets the offset.
  132.          *
  133.          * @param offset Must be greater or equal to 0.
  134.          * @return {@code this} instance.
  135.          */
  136.         public Builder setOffset(final int offset) {
  137.             if (offset < 0) {
  138.                 throw new IllegalArgumentException("offset cannot be negative");
  139.             }
  140.             this.offset = offset;
  141.             return this;
  142.         }

  143.     }

  144.     /**
  145.      * The end of stream marker.
  146.      */
  147.     public static final int END_OF_STREAM = -1;

  148.     /**
  149.      * Constructs a new {@link Builder}.
  150.      *
  151.      * @return a new {@link Builder}.
  152.      */
  153.     public static Builder builder() {
  154.         return new Builder();
  155.     }

  156.     private static int minPosLen(final byte[] data, final int defaultValue) {
  157.         requireNonNegative(defaultValue, "defaultValue");
  158.         return Math.min(defaultValue, data.length > 0 ? data.length : defaultValue);
  159.     }

  160.     private static int requireNonNegative(final int value, final String name) {
  161.         if (value < 0) {
  162.             throw new IllegalArgumentException(name + " cannot be negative");
  163.         }
  164.         return value;
  165.     }

  166.     /**
  167.      * The underlying data buffer.
  168.      */
  169.     private final byte[] data;

  170.     /**
  171.      * End Of Data.
  172.      *
  173.      * Similar to data.length, which is the last readable offset + 1.
  174.      */
  175.     private final int eod;

  176.     /**
  177.      * Current offset in the data buffer.
  178.      */
  179.     private int offset;

  180.     /**
  181.      * The current mark (if any).
  182.      */
  183.     private int markedOffset;

  184.     /**
  185.      * Constructs a new byte array input stream.
  186.      *
  187.      * @param data the buffer
  188.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
  189.      */
  190.     @Deprecated
  191.     public UnsynchronizedByteArrayInputStream(final byte[] data) {
  192.         this(data, data.length, 0, 0);
  193.     }

  194.     /**
  195.      * Constructs a new byte array input stream.
  196.      *
  197.      * @param data   the buffer
  198.      * @param offset the offset into the buffer
  199.      * @throws IllegalArgumentException if the offset is less than zero
  200.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
  201.      */
  202.     @Deprecated
  203.     public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset) {
  204.         this(data, data.length, Math.min(requireNonNegative(offset, "offset"), minPosLen(data, offset)), minPosLen(data, offset));
  205.     }

  206.     /**
  207.      * Constructs a new byte array input stream.
  208.      *
  209.      * @param data   the buffer
  210.      * @param offset the offset into the buffer
  211.      * @param length the length of the buffer
  212.      * @throws IllegalArgumentException if the offset or length less than zero
  213.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
  214.      */
  215.     @Deprecated
  216.     public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset, final int length) {
  217.         requireNonNegative(offset, "offset");
  218.         requireNonNegative(length, "length");
  219.         this.data = Objects.requireNonNull(data, "data");
  220.         this.eod = Math.min(minPosLen(data, offset) + length, data.length);
  221.         this.offset = minPosLen(data, offset);
  222.         this.markedOffset = minPosLen(data, offset);
  223.     }

  224.     private UnsynchronizedByteArrayInputStream(final byte[] data, final int eod, final int offset, final int markedOffset) {
  225.         this.data = Objects.requireNonNull(data, "data");
  226.         this.eod = eod;
  227.         this.offset = offset;
  228.         this.markedOffset = markedOffset;
  229.     }

  230.     @Override
  231.     public int available() {
  232.         return offset < eod ? eod - offset : 0;
  233.     }

  234.     @SuppressWarnings("sync-override")
  235.     @Override
  236.     public void mark(final int readLimit) {
  237.         this.markedOffset = this.offset;
  238.     }

  239.     @Override
  240.     public boolean markSupported() {
  241.         return true;
  242.     }

  243.     @Override
  244.     public int read() {
  245.         return offset < eod ? data[offset++] & 0xff : END_OF_STREAM;
  246.     }

  247.     @Override
  248.     public int read(final byte[] dest) {
  249.         Objects.requireNonNull(dest, "dest");
  250.         return read(dest, 0, dest.length);
  251.     }

  252.     @Override
  253.     public int read(final byte[] dest, final int off, final int len) {
  254.         Objects.requireNonNull(dest, "dest");
  255.         if (off < 0 || len < 0 || off + len > dest.length) {
  256.             throw new IndexOutOfBoundsException();
  257.         }

  258.         if (offset >= eod) {
  259.             return END_OF_STREAM;
  260.         }

  261.         int actualLen = eod - offset;
  262.         if (len < actualLen) {
  263.             actualLen = len;
  264.         }
  265.         if (actualLen <= 0) {
  266.             return 0;
  267.         }
  268.         System.arraycopy(data, offset, dest, off, actualLen);
  269.         offset += actualLen;
  270.         return actualLen;
  271.     }

  272.     @SuppressWarnings("sync-override")
  273.     @Override
  274.     public void reset() {
  275.         this.offset = this.markedOffset;
  276.     }

  277.     @Override
  278.     public long skip(final long n) {
  279.         if (n < 0) {
  280.             throw new IllegalArgumentException("Skipping backward is not supported");
  281.         }

  282.         long actualSkip = eod - offset;
  283.         if (n < actualSkip) {
  284.             actualSkip = n;
  285.         }

  286.         offset = Math.addExact(offset, Math.toIntExact(n));
  287.         return actualSkip;
  288.     }
  289. }