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