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}