    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.InputStream;
    020import java.util.Objects;
    021
    022import static java.lang.Math.min;
    023
    024/**
    025 * This is an alternative to {@link java.io.ByteArrayInputStream}
    026 * which removes the synchronization overhead for non-concurrent
    027 * access; as such this class is not thread-safe.
    028 *
    029 * @since 2.7
    030 */
    031//@NotThreadSafe
    032public class UnsynchronizedByteArrayInputStream extends InputStream {
    033
    034    /**
    035     * The end of stream marker.
    036     */
    037    public static final int END_OF_STREAM = -1;
    038
    039    /**
    040     * The underlying data buffer.
    041     */
    042    private final byte[] data;
    043
    044    /**
    045     * End Of Data.
    046     *
    047     * Similar to data.length,
    048     * i.e. the last readable offset + 1.
    049     */
    050    private final int eod;
    051
    052    /**
    053     * Current offset in the data buffer.
    054     */
    055    private int offset;
    056
    057    /**
    058     * The current mark (if any).
    059     */
    060    private int markedOffset;
    061
    062    /**
    063     * Creates a new byte array input stream.
    064     *
    065     * @param data the buffer
    066     */
    067    public UnsynchronizedByteArrayInputStream(final byte[] data) {
    068        this.data = Objects.requireNonNull(data, "data");
    069        this.offset = 0;
    070        this.eod = data.length;
    071        this.markedOffset = this.offset;
    072    }
    073
    074    /**
    075     * Creates a new byte array input stream.
    076     *
    077     * @param data the buffer
    078     * @param offset the offset into the buffer
    079     *
    080     * @throws IllegalArgumentException if the offset is less than zero
    081     */
    082    public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset) {
    083        Objects.requireNonNull(data, "data");
    084        if (offset < 0) {
    085            throw new IllegalArgumentException("offset cannot be negative");
    086        }
    087        this.data = data;
    088        this.offset = min(offset, data.length > 0 ? data.length : offset);
    089        this.eod = data.length;
    090        this.markedOffset = this.offset;
    091    }
    092
    093
    094    /**
    095     * Creates a new byte array input stream.
    096     *
    097     * @param data the buffer
    098     * @param offset the offset into the buffer
    099     * @param length the length of the buffer
    100     *
    101     * @throws IllegalArgumentException if the offset or length less than zero
    102     */
    103    public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset, final int length) {
    104        if (offset < 0) {
    105            throw new IllegalArgumentException("offset cannot be negative");
    106        }
    107        if (length < 0) {
    108            throw new IllegalArgumentException("length cannot be negative");
    109        }
    110        this.data = Objects.requireNonNull(data, "data");
    111        this.offset = min(offset, data.length > 0 ? data.length : offset);
    112        this.eod = min(this.offset + length, data.length);
    113        this.markedOffset = this.offset;
    114    }
    115
    116    @Override
    117    public int available() {
    118        return offset < eod ? eod - offset : 0;
    119    }
    120
    121    @Override
    122    public int read() {
    123        return offset < eod ? data[offset++] & 0xff : END_OF_STREAM;
    124    }
    125
    126    @Override
    127    public int read(final byte[] dest) {
    128        Objects.requireNonNull(dest, "dest");
    129        return read(dest, 0, dest.length);
    130    }
    131
    132    @Override
    133    public int read(final byte[] dest, final int off, final int len) {
    134        Objects.requireNonNull(dest, "dest");
    135        if (off < 0 || len < 0 || off + len > dest.length) {
    136            throw new IndexOutOfBoundsException();
    137        }
    138
    139        if (offset >= eod) {
    140            return END_OF_STREAM;
    141        }
    142
    143        int actualLen = eod - offset;
    144        if (len < actualLen) {
    145            actualLen = len;
    146        }
    147        if (actualLen <= 0) {
    148            return 0;
    149        }
    150        System.arraycopy(data, offset, dest, off, actualLen);
    151        offset += actualLen;
    152        return actualLen;
    153    }
    154
    155    @Override
    156    public long skip(final long n) {
    157        if (n < 0) {
    158            throw new IllegalArgumentException("Skipping backward is not supported");
    159        }
    160
    161        long actualSkip = eod - offset;
    162        if (n < actualSkip) {
    163            actualSkip = n;
    164        }
    165
    166        offset += actualSkip;
    167        return actualSkip;
    168    }
    169
    170    @Override
    171    public boolean markSupported() {
    172        return true;
    173    }
    174
    175    @SuppressWarnings("sync-override")
    176    @Override
    177    public void mark(final int readlimit) {
    178        this.markedOffset = this.offset;
    179    }
    180
    181    @SuppressWarnings("sync-override")
    182    @Override
    183    public void reset() {
    184        this.offset = this.markedOffset;
    185    }
    186}