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