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.ByteArrayInputStream;
020import java.io.IOException;
021import java.io.InputStream;
022import java.util.Objects;
023
024import org.apache.commons.io.build.AbstractOrigin;
025import org.apache.commons.io.build.AbstractStreamBuilder;
026
027/**
028 * This is an alternative to {@link java.io.ByteArrayInputStream} which removes the synchronization overhead for non-concurrent access; as such this class is
029 * not thread-safe.
030 * <p>
031 * To build an instance, use {@link Builder}.
032 * </p>
033 *
034 * @see Builder
035 * @see ByteArrayInputStream
036 * @since 2.7
037 */
038//@NotThreadSafe
039public class UnsynchronizedByteArrayInputStream extends InputStream {
040
041    // @formatter:off
042    /**
043     * Builds a new {@link UnsynchronizedByteArrayInputStream}.
044     *
045     * <p>
046     * Using a Byte Array:
047     * </p>
048     * <pre>{@code
049     * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder()
050     *   .setByteArray(byteArray)
051     *   .setOffset(0)
052     *   .setLength(byteArray.length)
053     *   .get();
054     * }
055     * </pre>
056     * <p>
057     * Using File IO:
058     * </p>
059     * <pre>{@code
060     * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder()
061     *   .setFile(file)
062     *   .setOffset(0)
063     *   .setLength(byteArray.length)
064     *   .get();
065     * }
066     * </pre>
067     * <p>
068     * Using NIO Path:
069     * </p>
070     * <pre>{@code
071     * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder()
072     *   .setPath(path)
073     *   .setOffset(0)
074     *   .setLength(byteArray.length)
075     *   .get();
076     * }
077     * </pre>
078     *
079     * @see #get()
080     */
081    // @formatter:on
082    public static class Builder extends AbstractStreamBuilder<UnsynchronizedByteArrayInputStream, Builder> {
083
084        private int offset;
085        private int length;
086
087        /**
088         * Builds a new {@link UnsynchronizedByteArrayInputStream}.
089         * <p>
090         * You must set input that supports {@code byte[]} on this builder, otherwise, this method throws an exception.
091         * </p>
092         * <p>
093         * This builder use the following aspects:
094         * </p>
095         * <ul>
096         * <li>{@code byte[]}</li>
097         * <li>offset</li>
098         * <li>length</li>
099         * </ul>
100         *
101         * @return a new instance.
102         * @throws UnsupportedOperationException if the origin cannot provide a byte[].
103         * @throws IllegalStateException         if the {@code origin} is {@code null}.
104         * @see AbstractOrigin#getByteArray()
105         */
106        @Override
107        public UnsynchronizedByteArrayInputStream get() throws IOException {
108            return new UnsynchronizedByteArrayInputStream(checkOrigin().getByteArray(), offset, length);
109        }
110
111        @Override
112        public Builder setByteArray(final byte[] origin) {
113            length = Objects.requireNonNull(origin, "origin").length;
114            return super.setByteArray(origin);
115        }
116
117        /**
118         * Sets the length.
119         *
120         * @param length Must be greater or equal to 0.
121         * @return this.
122         */
123        public Builder setLength(final int length) {
124            if (length < 0) {
125                throw new IllegalArgumentException("length cannot be negative");
126            }
127            this.length = length;
128            return this;
129        }
130
131        /**
132         * Sets the offset.
133         *
134         * @param offset Must be greater or equal to 0.
135         * @return this.
136         */
137        public Builder setOffset(final int offset) {
138            if (offset < 0) {
139                throw new IllegalArgumentException("offset cannot be negative");
140            }
141            this.offset = offset;
142            return this;
143        }
144
145    }
146
147    /**
148     * The end of stream marker.
149     */
150    public static final int END_OF_STREAM = -1;
151
152    /**
153     * Constructs a new {@link Builder}.
154     *
155     * @return a new {@link Builder}.
156     */
157    public static Builder builder() {
158        return new Builder();
159    }
160
161    private static int minPosLen(final byte[] data, final int defaultValue) {
162        requireNonNegative(defaultValue, "defaultValue");
163        return Math.min(defaultValue, data.length > 0 ? data.length : defaultValue);
164    }
165
166    private static int requireNonNegative(final int value, final String name) {
167        if (value < 0) {
168            throw new IllegalArgumentException(name + " cannot be negative");
169        }
170        return value;
171    }
172
173    /**
174     * The underlying data buffer.
175     */
176    private final byte[] data;
177
178    /**
179     * End Of Data.
180     *
181     * Similar to data.length, i.e. the last readable offset + 1.
182     */
183    private final int eod;
184
185    /**
186     * Current offset in the data buffer.
187     */
188    private int offset;
189
190    /**
191     * The current mark (if any).
192     */
193    private int markedOffset;
194
195    /**
196     * Constructs a new byte array input stream.
197     *
198     * @param data the buffer
199     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
200     */
201    @Deprecated
202    public UnsynchronizedByteArrayInputStream(final byte[] data) {
203        this(data, data.length, 0, 0);
204    }
205
206    /**
207     * Constructs a new byte array input stream.
208     *
209     * @param data   the buffer
210     * @param offset the offset into the buffer
211     *
212     * @throws IllegalArgumentException if the offset is less than zero
213     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
214     */
215    @Deprecated
216    public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset) {
217        this(data, data.length, Math.min(requireNonNegative(offset, "offset"), minPosLen(data, offset)), minPosLen(data, offset));
218    }
219
220    /**
221     * Constructs a new byte array input stream.
222     *
223     * @param data   the buffer
224     * @param offset the offset into the buffer
225     * @param length the length of the buffer
226     *
227     * @throws IllegalArgumentException if the offset or length less than zero
228     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
229     */
230    @Deprecated
231    public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset, final int length) {
232        requireNonNegative(offset, "offset");
233        requireNonNegative(length, "length");
234        this.data = Objects.requireNonNull(data, "data");
235        this.eod = Math.min(minPosLen(data, offset) + length, data.length);
236        this.offset = minPosLen(data, offset);
237        this.markedOffset = minPosLen(data, offset);
238    }
239
240    private UnsynchronizedByteArrayInputStream(final byte[] data, final int eod, final int offset, final int markedOffset) {
241        this.data = Objects.requireNonNull(data, "data");
242        this.eod = eod;
243        this.offset = offset;
244        this.markedOffset = markedOffset;
245    }
246
247    @Override
248    public int available() {
249        return offset < eod ? eod - offset : 0;
250    }
251
252    @SuppressWarnings("sync-override")
253    @Override
254    public void mark(final int readLimit) {
255        this.markedOffset = this.offset;
256    }
257
258    @Override
259    public boolean markSupported() {
260        return true;
261    }
262
263    @Override
264    public int read() {
265        return offset < eod ? data[offset++] & 0xff : END_OF_STREAM;
266    }
267
268    @Override
269    public int read(final byte[] dest) {
270        Objects.requireNonNull(dest, "dest");
271        return read(dest, 0, dest.length);
272    }
273
274    @Override
275    public int read(final byte[] dest, final int off, final int len) {
276        Objects.requireNonNull(dest, "dest");
277        if (off < 0 || len < 0 || off + len > dest.length) {
278            throw new IndexOutOfBoundsException();
279        }
280
281        if (offset >= eod) {
282            return END_OF_STREAM;
283        }
284
285        int actualLen = eod - offset;
286        if (len < actualLen) {
287            actualLen = len;
288        }
289        if (actualLen <= 0) {
290            return 0;
291        }
292        System.arraycopy(data, offset, dest, off, actualLen);
293        offset += actualLen;
294        return actualLen;
295    }
296
297    @SuppressWarnings("sync-override")
298    @Override
299    public void reset() {
300        this.offset = this.markedOffset;
301    }
302
303    @Override
304    public long skip(final long n) {
305        if (n < 0) {
306            throw new IllegalArgumentException("Skipping backward is not supported");
307        }
308
309        long actualSkip = eod - offset;
310        if (n < actualSkip) {
311            actualSkip = n;
312        }
313
314        offset = Math.addExact(offset, Math.toIntExact(n));
315        return actualSkip;
316    }
317}