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