    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 java.io.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         * Builds a new {@link UnsynchronizedByteArrayInputStream}.
    089         * <p>
    090         * You must set input that supports {@code byte[]} on this builder, otherwise, this method throws an exception.
    091         * </p>
    092         * <p>
    093         * This builder use the following aspects:
    094         * </p>
    095         * <ul>
    096         * <li>{@code byte[]}</li>
    097         * <li>offset</li>
    098         * <li>length</li>
    099         * </ul>
    100         *
    101         * @return a new instance.
    102         * @throws UnsupportedOperationException if the origin cannot provide a byte[].
    103         * @throws IllegalStateException         if the {@code origin} is {@code null}.
    104         * @see AbstractOrigin#getByteArray()
    105         */
    106        @Override
    107        public UnsynchronizedByteArrayInputStream get() throws IOException {
    108            return new UnsynchronizedByteArrayInputStream(checkOrigin().getByteArray(), offset, length);
    109        }
    110
    111        @Override
    112        public Builder setByteArray(final byte[] origin) {
    113            length = Objects.requireNonNull(origin, "origin").length;
    114            return super.setByteArray(origin);
    115        }
    116
    117        /**
    118         * Sets the length.
    119         *
    120         * @param length Must be greater or equal to 0.
    121         * @return this.
    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        /**
    132         * Sets the offset.
    133         *
    134         * @param offset Must be greater or equal to 0.
    135         * @return this.
    136         */
    137        public Builder setOffset(final int offset) {
    138            if (offset < 0) {
    139                throw new IllegalArgumentException("offset cannot be negative");
    140            }
    141            this.offset = offset;
    142            return this;
    143        }
    144
    145    }
    146
    147    /**
    148     * The end of stream marker.
    149     */
    150    public static final int END_OF_STREAM = -1;
    151
    152    /**
    153     * Constructs a new {@link Builder}.
    154     *
    155     * @return a new {@link Builder}.
    156     */
    157    public static Builder builder() {
    158        return new Builder();
    159    }
    160
    161    private static int minPosLen(final byte[] data, final int defaultValue) {
    162        requireNonNegative(defaultValue, "defaultValue");
    163        return Math.min(defaultValue, data.length > 0 ? data.length : defaultValue);
    164    }
    165
    166    private static int requireNonNegative(final int value, final String name) {
    167        if (value < 0) {
    168            throw new IllegalArgumentException(name + " cannot be negative");
    169        }
    170        return value;
    171    }
    172
    173    /**
    174     * The underlying data buffer.
    175     */
    176    private final byte[] data;
    177
    178    /**
    179     * End Of Data.
    180     *
    181     * Similar to data.length, i.e. the last readable offset + 1.
    182     */
    183    private final int eod;
    184
    185    /**
    186     * Current offset in the data buffer.
    187     */
    188    private int offset;
    189
    190    /**
    191     * The current mark (if any).
    192     */
    193    private int markedOffset;
    194
    195    /**
    196     * Constructs a new byte array input stream.
    197     *
    198     * @param data the buffer
    199     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    200     */
    201    @Deprecated
    202    public UnsynchronizedByteArrayInputStream(final byte[] data) {
    203        this(data, data.length, 0, 0);
    204    }
    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     *
    212     * @throws IllegalArgumentException if the offset is 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) {
    217        this(data, data.length, Math.min(requireNonNegative(offset, "offset"), minPosLen(data, offset)), minPosLen(data, offset));
    218    }
    219
    220    /**
    221     * Constructs a new byte array input stream.
    222     *
    223     * @param data   the buffer
    224     * @param offset the offset into the buffer
    225     * @param length the length of the buffer
    226     *
    227     * @throws IllegalArgumentException if the offset or length less than zero
    228     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    229     */
    230    @Deprecated
    231    public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset, final int length) {
    232        requireNonNegative(offset, "offset");
    233        requireNonNegative(length, "length");
    234        this.data = Objects.requireNonNull(data, "data");
    235        this.eod = Math.min(minPosLen(data, offset) + length, data.length);
    236        this.offset = minPosLen(data, offset);
    237        this.markedOffset = minPosLen(data, offset);
    238    }
    239
    240    private UnsynchronizedByteArrayInputStream(final byte[] data, final int eod, final int offset, final int markedOffset) {
    241        this.data = Objects.requireNonNull(data, "data");
    242        this.eod = eod;
    243        this.offset = offset;
    244        this.markedOffset = markedOffset;
    245    }
    246
    247    @Override
    248    public int available() {
    249        return offset < eod ? eod - offset : 0;
    250    }
    251
    252    @SuppressWarnings("sync-override")
    253    @Override
    254    public void mark(final int readLimit) {
    255        this.markedOffset = this.offset;
    256    }
    257
    258    @Override
    259    public boolean markSupported() {
    260        return true;
    261    }
    262
    263    @Override
    264    public int read() {
    265        return offset < eod ? data[offset++] & 0xff : END_OF_STREAM;
    266    }
    267
    268    @Override
    269    public int read(final byte[] dest) {
    270        Objects.requireNonNull(dest, "dest");
    271        return read(dest, 0, dest.length);
    272    }
    273
    274    @Override
    275    public int read(final byte[] dest, final int off, final int len) {
    276        Objects.requireNonNull(dest, "dest");
    277        if (off < 0 || len < 0 || off + len > dest.length) {
    278            throw new IndexOutOfBoundsException();
    279        }
    280
    281        if (offset >= eod) {
    282            return END_OF_STREAM;
    283        }
    284
    285        int actualLen = eod - offset;
    286        if (len < actualLen) {
    287            actualLen = len;
    288        }
    289        if (actualLen <= 0) {
    290            return 0;
    291        }
    292        System.arraycopy(data, offset, dest, off, actualLen);
    293        offset += actualLen;
    294        return actualLen;
    295    }
    296
    297    @SuppressWarnings("sync-override")
    298    @Override
    299    public void reset() {
    300        this.offset = this.markedOffset;
    301    }
    302
    303    @Override
    304    public long skip(final long n) {
    305        if (n < 0) {
    306            throw new IllegalArgumentException("Skipping backward is not supported");
    307        }
    308
    309        long actualSkip = eod - offset;
    310        if (n < actualSkip) {
    311            actualSkip = n;
    312        }
    313
    314        offset = Math.addExact(offset, Math.toIntExact(n));
    315        return actualSkip;
    316    }
    317}