    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.io.input;
    018
    019import java.io.ByteArrayInputStream;
    020import java.io.IOException;
    021import java.io.InputStream;
    022import java.util.Objects;
    023
    024import org.apache.commons.io.build.AbstractOrigin;
    025import org.apache.commons.io.build.AbstractStreamBuilder;
    026
    027/**
    028 * This is an alternative to {@link ByteArrayInputStream} which removes the synchronization overhead for non-concurrent access; as such this class is
    029 * not thread-safe.
    030 * <p>
    031 * To build an instance, use {@link Builder}.
    032 * </p>
    033 *
    034 * @see Builder
    035 * @see ByteArrayInputStream
    036 * @since 2.7
    037 */
    038//@NotThreadSafe
    039public class UnsynchronizedByteArrayInputStream extends InputStream {
    040
    041    // @formatter:off
    042    /**
    043     * Builds a new {@link UnsynchronizedByteArrayInputStream}.
    044     *
    045     * <p>
    046     * Using a Byte Array:
    047     * </p>
    048     * <pre>{@code
    049     * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder()
    050     *   .setByteArray(byteArray)
    051     *   .setOffset(0)
    052     *   .setLength(byteArray.length)
    053     *   .get();
    054     * }
    055     * </pre>
    056     * <p>
    057     * Using File IO:
    058     * </p>
    059     * <pre>{@code
    060     * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder()
    061     *   .setFile(file)
    062     *   .setOffset(0)
    063     *   .setLength(byteArray.length)
    064     *   .get();
    065     * }
    066     * </pre>
    067     * <p>
    068     * Using NIO Path:
    069     * </p>
    070     * <pre>{@code
    071     * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder()
    072     *   .setPath(path)
    073     *   .setOffset(0)
    074     *   .setLength(byteArray.length)
    075     *   .get();
    076     * }
    077     * </pre>
    078     *
    079     * @see #get()
    080     */
    081    // @formatter:on
    082    public static class Builder extends AbstractStreamBuilder<UnsynchronizedByteArrayInputStream, Builder> {
    083
    084        private int offset;
    085        private int length;
    086
    087        /**
    088         * Constructs a builder of {@link UnsynchronizedByteArrayInputStream}.
    089         */
    090        public Builder() {
    091            // empty
    092        }
    093
    094        /**
    095         * Builds a new {@link UnsynchronizedByteArrayInputStream}.
    096         * <p>
    097         * You must set an aspect that supports {@code byte[]} on this builder, otherwise, this method throws an exception.
    098         * </p>
    099         * <p>
    100         * This builder uses the following aspects:
    101         * </p>
    102         * <ul>
    103         * <li>{@code byte[]}</li>
    104         * <li>offset</li>
    105         * <li>length</li>
    106         * </ul>
    107         *
    108         * @return a new instance.
    109         * @throws UnsupportedOperationException if the origin cannot provide a {@code byte[]}.
    110         * @throws IllegalStateException         if the {@code origin} is {@code null}.
    111         * @throws IOException                   if an I/O error occurs converting to an {@code byte[]} using {@link AbstractOrigin#getByteArray()}.
    112         * @see AbstractOrigin#getByteArray()
    113         * @see #getUnchecked()
    114         */
    115        @Override
    116        public UnsynchronizedByteArrayInputStream get() throws IOException {
    117            return new UnsynchronizedByteArrayInputStream(checkOrigin().getByteArray(), offset, length);
    118        }
    119
    120        @Override
    121        public Builder setByteArray(final byte[] origin) {
    122            length = Objects.requireNonNull(origin, "origin").length;
    123            return super.setByteArray(origin);
    124        }
    125
    126        /**
    127         * Sets the length.
    128         *
    129         * @param length Must be greater or equal to 0.
    130         * @return {@code this} instance.
    131         */
    132        public Builder setLength(final int length) {
    133            if (length < 0) {
    134                throw new IllegalArgumentException("length cannot be negative");
    135            }
    136            this.length = length;
    137            return this;
    138        }
    139
    140        /**
    141         * Sets the offset.
    142         *
    143         * @param offset Must be greater or equal to 0.
    144         * @return {@code this} instance.
    145         */
    146        public Builder setOffset(final int offset) {
    147            if (offset < 0) {
    148                throw new IllegalArgumentException("offset cannot be negative");
    149            }
    150            this.offset = offset;
    151            return this;
    152        }
    153
    154    }
    155
    156    /**
    157     * The end of stream marker.
    158     */
    159    public static final int END_OF_STREAM = -1;
    160
    161    /**
    162     * Constructs a new {@link Builder}.
    163     *
    164     * @return a new {@link Builder}.
    165     */
    166    public static Builder builder() {
    167        return new Builder();
    168    }
    169
    170    private static int minPosLen(final byte[] data, final int defaultValue) {
    171        requireNonNegative(defaultValue, "defaultValue");
    172        return Math.min(defaultValue, data.length > 0 ? data.length : defaultValue);
    173    }
    174
    175    private static int requireNonNegative(final int value, final String name) {
    176        if (value < 0) {
    177            throw new IllegalArgumentException(name + " cannot be negative");
    178        }
    179        return value;
    180    }
    181
    182    /**
    183     * The underlying data buffer.
    184     */
    185    private final byte[] data;
    186
    187    /**
    188     * End Of Data.
    189     *
    190     * Similar to data.length, which is the last readable offset + 1.
    191     */
    192    private final int eod;
    193
    194    /**
    195     * Current offset in the data buffer.
    196     */
    197    private int offset;
    198
    199    /**
    200     * The current mark (if any).
    201     */
    202    private int markedOffset;
    203
    204    /**
    205     * Constructs a new byte array input stream.
    206     *
    207     * @param data the buffer
    208     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    209     */
    210    @Deprecated
    211    public UnsynchronizedByteArrayInputStream(final byte[] data) {
    212        this(data, data.length, 0, 0);
    213    }
    214
    215    /**
    216     * Constructs a new byte array input stream.
    217     *
    218     * @param data   the buffer
    219     * @param offset the offset into the buffer
    220     * @throws IllegalArgumentException if the offset is less than zero
    221     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    222     */
    223    @Deprecated
    224    public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset) {
    225        this(data, data.length, Math.min(requireNonNegative(offset, "offset"), minPosLen(data, offset)), minPosLen(data, offset));
    226    }
    227
    228    /**
    229     * Constructs a new byte array input stream.
    230     *
    231     * @param data   the buffer
    232     * @param offset the offset into the buffer
    233     * @param length the length of the buffer
    234     * @throws IllegalArgumentException if the offset or length less than zero
    235     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    236     */
    237    @Deprecated
    238    public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset, final int length) {
    239        requireNonNegative(offset, "offset");
    240        requireNonNegative(length, "length");
    241        this.data = Objects.requireNonNull(data, "data");
    242        this.eod = Math.min(minPosLen(data, offset) + length, data.length);
    243        this.offset = minPosLen(data, offset);
    244        this.markedOffset = minPosLen(data, offset);
    245    }
    246
    247    private UnsynchronizedByteArrayInputStream(final byte[] data, final int eod, final int offset, final int markedOffset) {
    248        this.data = Objects.requireNonNull(data, "data");
    249        this.eod = eod;
    250        this.offset = offset;
    251        this.markedOffset = markedOffset;
    252    }
    253
    254    @Override
    255    public int available() {
    256        return offset < eod ? eod - offset : 0;
    257    }
    258
    259    @SuppressWarnings("sync-override")
    260    @Override
    261    public void mark(final int readLimit) {
    262        this.markedOffset = this.offset;
    263    }
    264
    265    @Override
    266    public boolean markSupported() {
    267        return true;
    268    }
    269
    270    @Override
    271    public int read() {
    272        return offset < eod ? data[offset++] & 0xff : END_OF_STREAM;
    273    }
    274
    275    @Override
    276    public int read(final byte[] dest) {
    277        Objects.requireNonNull(dest, "dest");
    278        return read(dest, 0, dest.length);
    279    }
    280
    281    @Override
    282    public int read(final byte[] dest, final int off, final int len) {
    283        Objects.requireNonNull(dest, "dest");
    284        if (off < 0 || len < 0 || off + len > dest.length) {
    285            throw new IndexOutOfBoundsException();
    286        }
    287
    288        if (offset >= eod) {
    289            return END_OF_STREAM;
    290        }
    291
    292        int actualLen = eod - offset;
    293        if (len < actualLen) {
    294            actualLen = len;
    295        }
    296        if (actualLen <= 0) {
    297            return 0;
    298        }
    299        System.arraycopy(data, offset, dest, off, actualLen);
    300        offset += actualLen;
    301        return actualLen;
    302    }
    303
    304    @SuppressWarnings("sync-override")
    305    @Override
    306    public void reset() {
    307        this.offset = this.markedOffset;
    308    }
    309
    310    @Override
    311    public long skip(final long n) {
    312        if (n < 0) {
    313            throw new IllegalArgumentException("Skipping backward is not supported");
    314        }
    315
    316        long actualSkip = eod - offset;
    317        if (n < actualSkip) {
    318            actualSkip = n;
    319        }
    320
    321        offset = Math.addExact(offset, Math.toIntExact(n));
    322        return actualSkip;
    323    }
    324}