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