    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        Objects.requireNonNull(data);
    069        this.data = data;
    070        this.offset = 0;
    071        this.eod = data.length;
    072        this.markedOffset = this.offset;
    073    }
    074
    075    /**
    076     * Creates a new byte array input stream.
    077     *
    078     * @param data the buffer
    079     * @param offset the offset into the buffer
    080     *
    081     * @throws IllegalArgumentException if the offset is less than zero
    082     */
    083    public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset) {
    084        Objects.requireNonNull(data);
    085        if (offset < 0) {
    086            throw new IllegalArgumentException("offset cannot be negative");
    087        }
    088        this.data = data;
    089        this.offset = min(offset, data.length > 0 ? data.length: offset);
    090        this.eod = data.length;
    091        this.markedOffset = this.offset;
    092    }
    093
    094
    095    /**
    096     * Creates a new byte array input stream.
    097     *
    098     * @param data the buffer
    099     * @param offset the offset into the buffer
    100     * @param length the length of the buffer
    101     *
    102     * @throws IllegalArgumentException if the offset or length less than zero
    103     */
    104    public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset, final int length) {
    105        Objects.requireNonNull(data);
    106        if (offset < 0) {
    107            throw new IllegalArgumentException("offset cannot be negative");
    108        }
    109        if (length < 0) {
    110            throw new IllegalArgumentException("length cannot be negative");
    111        }
    112        this.data = data;
    113        this.offset = min(offset, data.length > 0 ? data.length : offset);
    114        this.eod = min(this.offset + length, data.length);
    115        this.markedOffset = this.offset;
    116    }
    117
    118    @Override
    119    public int available() {
    120        return offset < eod ? eod - offset : 0;
    121    }
    122
    123    @Override
    124    public int read() {
    125        return offset < eod ? data[offset++] & 0xff : END_OF_STREAM;
    126    }
    127
    128    @Override
    129    public int read(final byte[] b) {
    130        Objects.requireNonNull(b);
    131        return read(b, 0, b.length);
    132    }
    133
    134    @Override
    135    public int read(final byte[] b, final int off, final int len) {
    136        Objects.requireNonNull(b);
    137        if (off < 0 || len < 0 || off + len > b.length) {
    138            throw new IndexOutOfBoundsException();
    139        }
    140
    141        if (offset >= eod) {
    142            return END_OF_STREAM;
    143        }
    144
    145        int actualLen = eod - offset;
    146        if (len < actualLen) {
    147            actualLen = len;
    148        }
    149        if (actualLen <= 0) {
    150            return 0;
    151        }
    152        System.arraycopy(data, offset, b, off, actualLen);
    153        offset += actualLen;
    154        return actualLen;
    155    }
    156
    157    @Override
    158    public long skip(final long n) {
    159        if(n < 0) {
    160            throw new IllegalArgumentException("Skipping backward is not supported");
    161        }
    162
    163        long actualSkip = eod - offset;
    164        if (n < actualSkip) {
    165            actualSkip = n;
    166        }
    167
    168        offset += actualSkip;
    169        return actualSkip;
    170    }
    171
    172    @Override
    173    public boolean markSupported() {
    174        return true;
    175    }
    176
    177    @SuppressWarnings("sync-override")
    178    @Override
    179    public void mark(final int readlimit) {
    180        this.markedOffset = this.offset;
    181    }
    182
    183    @SuppressWarnings("sync-override")
    184    @Override
    185    public void reset() {
    186        this.offset = this.markedOffset;
    187    }
    188}