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}