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.  *      https://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.         private byte[] checkOriginByteArray() throws IOException {
  88.             return checkOrigin().getByteArray();
  89.         }

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

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

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

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

  146.     }

  147.     /**
  148.      * The end of stream marker.
  149.      */
  150.     public static final int END_OF_STREAM = -1;

  151.     /**
  152.      * Constructs a new {@link Builder}.
  153.      *
  154.      * @return a new {@link Builder}.
  155.      */
  156.     public static Builder builder() {
  157.         return new Builder();
  158.     }

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

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

  169.     /**
  170.      * The underlying data buffer.
  171.      */
  172.     private final byte[] data;

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

  179.     /**
  180.      * Current offset in the data buffer.
  181.      */
  182.     private int offset;

  183.     /**
  184.      * The current mark (if any).
  185.      */
  186.     private int markedOffset;

  187.     private UnsynchronizedByteArrayInputStream(final Builder builder) throws IOException {
  188.         this(builder.checkOriginByteArray(), builder.offset, builder.length);
  189.     }

  190.     /**
  191.      * Constructs a new byte array input stream.
  192.      *
  193.      * @param data the buffer
  194.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
  195.      */
  196.     @Deprecated
  197.     public UnsynchronizedByteArrayInputStream(final byte[] data) {
  198.         this(data, data.length, 0, 0);
  199.     }

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

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

  230.     private UnsynchronizedByteArrayInputStream(final byte[] data, final int eod, final int offset, final int markedOffset) {
  231.         this.data = Objects.requireNonNull(data, "data");
  232.         this.eod = eod;
  233.         this.offset = offset;
  234.         this.markedOffset = markedOffset;
  235.     }

  236.     @Override
  237.     public int available() {
  238.         return offset < eod ? eod - offset : 0;
  239.     }

  240.     @SuppressWarnings("sync-override")
  241.     @Override
  242.     public void mark(final int readLimit) {
  243.         this.markedOffset = this.offset;
  244.     }

  245.     @Override
  246.     public boolean markSupported() {
  247.         return true;
  248.     }

  249.     @Override
  250.     public int read() {
  251.         return offset < eod ? data[offset++] & 0xff : END_OF_STREAM;
  252.     }

  253.     @Override
  254.     public int read(final byte[] dest) {
  255.         Objects.requireNonNull(dest, "dest");
  256.         return read(dest, 0, dest.length);
  257.     }

  258.     @Override
  259.     public int read(final byte[] dest, final int off, final int len) {
  260.         Objects.requireNonNull(dest, "dest");
  261.         if (off < 0 || len < 0 || off + len > dest.length) {
  262.             throw new IndexOutOfBoundsException();
  263.         }

  264.         if (offset >= eod) {
  265.             return END_OF_STREAM;
  266.         }

  267.         int actualLen = eod - offset;
  268.         if (len < actualLen) {
  269.             actualLen = len;
  270.         }
  271.         if (actualLen <= 0) {
  272.             return 0;
  273.         }
  274.         System.arraycopy(data, offset, dest, off, actualLen);
  275.         offset += actualLen;
  276.         return actualLen;
  277.     }

  278.     @SuppressWarnings("sync-override")
  279.     @Override
  280.     public void reset() {
  281.         this.offset = this.markedOffset;
  282.     }

  283.     @Override
  284.     public long skip(final long n) {
  285.         if (n < 0) {
  286.             throw new IllegalArgumentException("Skipping backward is not supported");
  287.         }

  288.         long actualSkip = eod - offset;
  289.         if (n < actualSkip) {
  290.             actualSkip = n;
  291.         }

  292.         offset = Math.addExact(offset, Math.toIntExact(n));
  293.         return actualSkip;
  294.     }
  295. }