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