    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, see {@link Builder}.
    032 * </p>
    033 *
    034 * @see ByteArrayInputStream
    035 * @since 2.7
    036 */
    037//@NotThreadSafe
    038public class UnsynchronizedByteArrayInputStream extends InputStream {
    039
    040    /**
    041     * Builds a new {@link UnsynchronizedByteArrayInputStream} instance.
    042     * <p>
    043     * Using a Byte Array:
    044     * </p>
    045     *
    046     * <pre>{@code
    047     * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder().setByteArray(byteArray).setOffset(0).setLength(byteArray.length)
    048     *         .get();
    049     * }
    050     * </pre>
    051     * <p>
    052     * Using File IO:
    053     * </p>
    054     *
    055     * <pre>{@code
    056     * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder().setFile(file).setOffset(0).setLength(byteArray.length).get();
    057     * }
    058     * </pre>
    059     * <p>
    060     * Using NIO Path:
    061     * </p>
    062     *
    063     * <pre>{@code
    064     * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder().setPath(path).setOffset(0).setLength(byteArray.length).get();
    065     * }
    066     * </pre>
    067     */
    068    public static class Builder extends AbstractStreamBuilder<UnsynchronizedByteArrayInputStream, Builder> {
    069
    070        private int offset;
    071        private int length;
    072
    073        /**
    074         * Constructs a new instance.
    075         * <p>
    076         * This builder use the aspects byte[], offset and length.
    077         * </p>
    078         * <p>
    079         * You must provide an origin that can be converted to a byte[] by this builder, otherwise, this call will throw an
    080         * {@link UnsupportedOperationException}.
    081         * </p>
    082         *
    083         * @return a new instance.
    084         * @throws UnsupportedOperationException if the origin cannot provide a byte[].
    085         * @throws IllegalStateException         if the {@code origin} is {@code null}.
    086         * @see AbstractOrigin#getByteArray()
    087         */
    088        @Override
    089        public UnsynchronizedByteArrayInputStream get() throws IOException {
    090            return new UnsynchronizedByteArrayInputStream(checkOrigin().getByteArray(), offset, length);
    091        }
    092
    093        @Override
    094        public Builder setByteArray(final byte[] origin) {
    095            length = Objects.requireNonNull(origin, "origin").length;
    096            return super.setByteArray(origin);
    097        }
    098
    099        /**
    100         * Sets the length.
    101         *
    102         * @param length Must be greater or equal to 0.
    103         * @return this.
    104         */
    105        public Builder setLength(final int length) {
    106            if (length < 0) {
    107                throw new IllegalArgumentException("length cannot be negative");
    108            }
    109            this.length = length;
    110            return this;
    111        }
    112
    113        /**
    114         * Sets the offset.
    115         *
    116         * @param offset Must be greater or equal to 0.
    117         * @return this.
    118         */
    119        public Builder setOffset(final int offset) {
    120            if (offset < 0) {
    121                throw new IllegalArgumentException("offset cannot be negative");
    122            }
    123            this.offset = offset;
    124            return this;
    125        }
    126
    127    }
    128
    129    /**
    130     * The end of stream marker.
    131     */
    132    public static final int END_OF_STREAM = -1;
    133
    134    /**
    135     * Constructs a new {@link Builder}.
    136     *
    137     * @return a new {@link Builder}.
    138     */
    139    public static Builder builder() {
    140        return new Builder();
    141    }
    142
    143    private static int minPosLen(final byte[] data, final int defaultValue) {
    144        requireNonNegative(defaultValue, "defaultValue");
    145        return Math.min(defaultValue, data.length > 0 ? data.length : defaultValue);
    146    }
    147
    148    private static int requireNonNegative(final int value, final String name) {
    149        if (value < 0) {
    150            throw new IllegalArgumentException(name + " cannot be negative");
    151        }
    152        return value;
    153    }
    154
    155    /**
    156     * The underlying data buffer.
    157     */
    158    private final byte[] data;
    159
    160    /**
    161     * End Of Data.
    162     *
    163     * Similar to data.length, i.e. the last readable offset + 1.
    164     */
    165    private final int eod;
    166
    167    /**
    168     * Current offset in the data buffer.
    169     */
    170    private int offset;
    171
    172    /**
    173     * The current mark (if any).
    174     */
    175    private int markedOffset;
    176
    177    /**
    178     * Constructs a new byte array input stream.
    179     *
    180     * @param data the buffer
    181     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    182     */
    183    @Deprecated
    184    public UnsynchronizedByteArrayInputStream(final byte[] data) {
    185        this(data, data.length, 0, 0);
    186    }
    187
    188    /**
    189     * Constructs a new byte array input stream.
    190     *
    191     * @param data   the buffer
    192     * @param offset the offset into the buffer
    193     *
    194     * @throws IllegalArgumentException if the offset is less than zero
    195     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    196     */
    197    @Deprecated
    198    public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset) {
    199        this(data, data.length, Math.min(requireNonNegative(offset, "offset"), minPosLen(data, offset)), minPosLen(data, offset));
    200    }
    201
    202    /**
    203     * Constructs a new byte array input stream.
    204     *
    205     * @param data   the buffer
    206     * @param offset the offset into the buffer
    207     * @param length the length of the buffer
    208     *
    209     * @throws IllegalArgumentException if the offset or length less than zero
    210     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    211     */
    212    @Deprecated
    213    public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset, final int length) {
    214        requireNonNegative(offset, "offset");
    215        requireNonNegative(length, "length");
    216        this.data = Objects.requireNonNull(data, "data");
    217        this.eod = Math.min(minPosLen(data, offset) + length, data.length);
    218        this.offset = minPosLen(data, offset);
    219        this.markedOffset = minPosLen(data, offset);
    220    }
    221
    222    private UnsynchronizedByteArrayInputStream(byte[] data, int eod, int offset, int markedOffset) {
    223        this.data = Objects.requireNonNull(data, "data");
    224        this.eod = eod;
    225        this.offset = offset;
    226        this.markedOffset = markedOffset;
    227    }
    228
    229    @Override
    230    public int available() {
    231        return offset < eod ? eod - offset : 0;
    232    }
    233
    234    @SuppressWarnings("sync-override")
    235    @Override
    236    public void mark(final int readLimit) {
    237        this.markedOffset = this.offset;
    238    }
    239
    240    @Override
    241    public boolean markSupported() {
    242        return true;
    243    }
    244
    245    @Override
    246    public int read() {
    247        return offset < eod ? data[offset++] & 0xff : END_OF_STREAM;
    248    }
    249
    250    @Override
    251    public int read(final byte[] dest) {
    252        Objects.requireNonNull(dest, "dest");
    253        return read(dest, 0, dest.length);
    254    }
    255
    256    @Override
    257    public int read(final byte[] dest, final int off, final int len) {
    258        Objects.requireNonNull(dest, "dest");
    259        if (off < 0 || len < 0 || off + len > dest.length) {
    260            throw new IndexOutOfBoundsException();
    261        }
    262
    263        if (offset >= eod) {
    264            return END_OF_STREAM;
    265        }
    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
    279    @SuppressWarnings("sync-override")
    280    @Override
    281    public void reset() {
    282        this.offset = this.markedOffset;
    283    }
    284
    285    @Override
    286    public long skip(final long n) {
    287        if (n < 0) {
    288            throw new IllegalArgumentException("Skipping backward is not supported");
    289        }
    290
    291        long actualSkip = eod - offset;
    292        if (n < actualSkip) {
    293            actualSkip = n;
    294        }
    295
    296        offset = Math.addExact(offset, Math.toIntExact(n));
    297        return actualSkip;
    298    }
    299}