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