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