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