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