• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 package org.apache.commons.io.input;
18 
19 import static java.lang.Math.min;
20 
21 import java.io.ByteArrayInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.util.Objects;
25 
26 import org.apache.commons.io.build.AbstractOrigin;
27 import org.apache.commons.io.build.AbstractStreamBuilder;
28 
29 /**
30  * This is an alternative to {@link java.io.ByteArrayInputStream} which removes the synchronization overhead for non-concurrent access; as such this class is
31  * not thread-safe.
32  * <p>
33  * To build an instance, see {@link Builder}.
34  * </p>
35  *
36  * @see ByteArrayInputStream
37  * @since 2.7
38  */
39 //@NotThreadSafe
40 public class UnsynchronizedByteArrayInputStream extends InputStream {
41 
42     /**
43      * Builds a new {@link UnsynchronizedByteArrayInputStream} instance.
44      * <p>
45      * Using a Byte Array:
46      * </p>
47      *
48      * <pre>{@code
49      * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder()
50      *   .setByteArray(byteArray)
51      *   .setOffset(0)
52      *   .setLength(byteArray.length)
53      *   .get();}
54      * </pre>
55      * <p>
56      * Using File IO:
57      * </p>
58      *
59      * <pre>{@code
60      * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder()
61      *   .setFile(file)
62      *   .setOffset(0)
63      *   .setLength(byteArray.length)
64      *   .get();}
65      * </pre>
66      * <p>
67      * Using NIO Path:
68      * </p>
69      *
70      * <pre>{@code
71      * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder()
72      *   .setPath(path)
73      *   .setOffset(0)
74      *   .setLength(byteArray.length)
75      *   .get();}
76      * </pre>
77      */
78     public static class Builder extends AbstractStreamBuilder<UnsynchronizedByteArrayInputStream, Builder> {
79 
80         private int offset;
81         private int length;
82 
83         /**
84          * Constructs a new instance.
85          * <p>
86          * This builder use the aspects byte[], offset and length.
87          * </p>
88          * <p>
89          * You must provide an origin that can be converted to a byte[] by this builder, otherwise, this call will throw an
90          * {@link UnsupportedOperationException}.
91          * </p>
92          *
93          * @return a new instance.
94          * @throws UnsupportedOperationException if the origin cannot provide a byte[].
95          * @throws IllegalStateException if the {@code origin} is {@code null}.
96          * @see AbstractOrigin#getByteArray()
97          */
98         @Override
get()99         public UnsynchronizedByteArrayInputStream get() throws IOException {
100             return new UnsynchronizedByteArrayInputStream(checkOrigin().getByteArray(), offset, length);
101         }
102 
103         @Override
setByteArray(final byte[] origin)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          */
setLength(final int length)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          */
setOffset(final int offset)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      */
builder()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
UnsynchronizedByteArrayInputStream(final byte[] data)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
UnsynchronizedByteArrayInputStream(final byte[] data, final int offset)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
UnsynchronizedByteArrayInputStream(final byte[] data, final int offset, final int length)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
available()235     public int available() {
236         return offset < eod ? eod - offset : 0;
237     }
238 
239     @SuppressWarnings("sync-override")
240     @Override
mark(final int readlimit)241     public void mark(final int readlimit) {
242         this.markedOffset = this.offset;
243     }
244 
245     @Override
markSupported()246     public boolean markSupported() {
247         return true;
248     }
249 
250     @Override
read()251     public int read() {
252         return offset < eod ? data[offset++] & 0xff : END_OF_STREAM;
253     }
254 
255     @Override
read(final byte[] dest)256     public int read(final byte[] dest) {
257         Objects.requireNonNull(dest, "dest");
258         return read(dest, 0, dest.length);
259     }
260 
261     @Override
read(final byte[] dest, final int off, final int len)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
reset()286     public void reset() {
287         this.offset = this.markedOffset;
288     }
289 
290     @Override
skip(final long n)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 }
305