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 * https://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 java.io.ByteArrayInputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.util.Objects;
23
24 import org.apache.commons.io.IOUtils;
25 import org.apache.commons.io.build.AbstractOrigin;
26 import org.apache.commons.io.build.AbstractStreamBuilder;
27
28 /**
29 * This is an alternative to {@link ByteArrayInputStream} which removes the synchronization overhead for non-concurrent access; as such this class is
30 * not thread-safe.
31 * <p>
32 * To build an instance, use {@link Builder}.
33 * </p>
34 *
35 * @see Builder
36 * @see ByteArrayInputStream
37 * @since 2.7
38 */
39 //@NotThreadSafe
40 public class UnsynchronizedByteArrayInputStream extends InputStream {
41
42 // @formatter:off
43 /**
44 * Builds a new {@link UnsynchronizedByteArrayInputStream}.
45 *
46 * <p>
47 * Using a Byte Array:
48 * </p>
49 * <pre>{@code
50 * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder()
51 * .setByteArray(byteArray)
52 * .setOffset(0)
53 * .setLength(byteArray.length)
54 * .get();
55 * }
56 * </pre>
57 * <p>
58 * Using File IO:
59 * </p>
60 * <pre>{@code
61 * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder()
62 * .setFile(file)
63 * .setOffset(0)
64 * .setLength(byteArray.length)
65 * .get();
66 * }
67 * </pre>
68 * <p>
69 * Using NIO Path:
70 * </p>
71 * <pre>{@code
72 * UnsynchronizedByteArrayInputStream s = UnsynchronizedByteArrayInputStream.builder()
73 * .setPath(path)
74 * .setOffset(0)
75 * .setLength(byteArray.length)
76 * .get();
77 * }
78 * </pre>
79 *
80 * @see #get()
81 */
82 // @formatter:on
83 public static class Builder extends AbstractStreamBuilder<UnsynchronizedByteArrayInputStream, Builder> {
84
85 private int offset;
86 private int length;
87
88 /**
89 * Constructs a builder of {@link UnsynchronizedByteArrayInputStream}.
90 */
91 public Builder() {
92 // empty
93 }
94
95 /**
96 * Builds a new {@link UnsynchronizedByteArrayInputStream}.
97 * <p>
98 * You must set an aspect that supports {@code byte[]} on this builder, otherwise, this method throws an exception.
99 * </p>
100 * <p>
101 * This builder uses the following aspects:
102 * </p>
103 * <ul>
104 * <li>{@code byte[]}</li>
105 * <li>offset</li>
106 * <li>length</li>
107 * </ul>
108 *
109 * @return a new instance.
110 * @throws UnsupportedOperationException if the origin cannot provide a {@code byte[]}.
111 * @throws IllegalStateException if the {@code origin} is {@code null}.
112 * @throws IOException if an I/O error occurs converting to an {@code byte[]} using {@link AbstractOrigin#getByteArray()}.
113 * @see AbstractOrigin#getByteArray()
114 * @see #getUnchecked()
115 */
116 @Override
117 public UnsynchronizedByteArrayInputStream get() throws IOException {
118 return new UnsynchronizedByteArrayInputStream(this);
119 }
120
121 @Override
122 public Builder setByteArray(final byte[] origin) {
123 length = Objects.requireNonNull(origin, "origin").length;
124 return super.setByteArray(origin);
125 }
126
127 /**
128 * Sets the length.
129 *
130 * @param length Must be greater or equal to 0.
131 * @return {@code this} instance.
132 */
133 public Builder setLength(final int length) {
134 if (length < 0) {
135 throw new IllegalArgumentException("length cannot be negative");
136 }
137 this.length = length;
138 return this;
139 }
140
141 /**
142 * Sets the offset.
143 *
144 * @param offset Must be greater or equal to 0.
145 * @return {@code this} instance.
146 */
147 public Builder setOffset(final int offset) {
148 if (offset < 0) {
149 throw new IllegalArgumentException("offset cannot be negative");
150 }
151 this.offset = offset;
152 return this;
153 }
154
155 }
156
157 /**
158 * The end of stream marker.
159 */
160 public static final int END_OF_STREAM = -1;
161
162 /**
163 * Constructs a new {@link Builder}.
164 *
165 * @return a new {@link Builder}.
166 */
167 public static Builder builder() {
168 return new Builder();
169 }
170
171 private static int minPosLen(final byte[] data, final int defaultValue) {
172 requireNonNegative(defaultValue, "defaultValue");
173 return Math.min(defaultValue, data.length > 0 ? data.length : defaultValue);
174 }
175
176 private static int requireNonNegative(final int value, final String name) {
177 if (value < 0) {
178 throw new IllegalArgumentException(name + " cannot be negative");
179 }
180 return value;
181 }
182
183 /**
184 * The underlying data buffer.
185 */
186 private final byte[] data;
187
188 /**
189 * End Of Data.
190 *
191 * Similar to data.length, which is the last readable offset + 1.
192 */
193 private final int eod;
194
195 /**
196 * Current offset in the data buffer.
197 */
198 private int offset;
199
200 /**
201 * The current mark (if any).
202 */
203 private int markedOffset;
204
205 private UnsynchronizedByteArrayInputStream(final Builder builder) throws IOException {
206 this(builder.getByteArray(), builder.offset, builder.length);
207 }
208
209 /**
210 * Constructs a new byte array input stream.
211 *
212 * @param data the buffer.
213 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
214 */
215 @Deprecated
216 public UnsynchronizedByteArrayInputStream(final byte[] data) {
217 this(data, data.length, 0, 0);
218 }
219
220 /**
221 * Constructs a new byte array input stream.
222 *
223 * @param data the buffer.
224 * @param offset the offset into the buffer.
225 * @throws IllegalArgumentException if the offset is less than zero.
226 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
227 */
228 @Deprecated
229 public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset) {
230 this(data, data.length, Math.min(requireNonNegative(offset, "offset"), minPosLen(data, offset)), minPosLen(data, offset));
231 }
232
233 /**
234 * Constructs a new byte array input stream.
235 *
236 * @param data the buffer.
237 * @param offset the offset into the buffer.
238 * @param length the length of the buffer.
239 * @throws IllegalArgumentException if the offset or length less than zero.
240 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
241 */
242 @Deprecated
243 public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset, final int length) {
244 requireNonNegative(offset, "offset");
245 requireNonNegative(length, "length");
246 this.data = Objects.requireNonNull(data, "data");
247 this.eod = Math.min(minPosLen(data, offset) + length, data.length);
248 this.offset = minPosLen(data, offset);
249 this.markedOffset = minPosLen(data, offset);
250 }
251
252 private UnsynchronizedByteArrayInputStream(final byte[] data, final int eod, final int offset, final int markedOffset) {
253 this.data = Objects.requireNonNull(data, "data");
254 this.eod = eod;
255 this.offset = offset;
256 this.markedOffset = markedOffset;
257 }
258
259 @Override
260 public int available() {
261 return offset < eod ? eod - offset : 0;
262 }
263
264 @SuppressWarnings("sync-override")
265 @Override
266 public void mark(final int readLimit) {
267 this.markedOffset = this.offset;
268 }
269
270 @Override
271 public boolean markSupported() {
272 return true;
273 }
274
275 @Override
276 public int read() {
277 return offset < eod ? data[offset++] & 0xff : END_OF_STREAM;
278 }
279
280 @Override
281 public int read(final byte[] dest) {
282 Objects.requireNonNull(dest, "dest");
283 return read(dest, 0, dest.length);
284 }
285
286 @Override
287 public int read(final byte[] dest, final int off, final int len) {
288 IOUtils.checkFromIndexSize(dest, off, len);
289 if (len == 0) {
290 return 0;
291 }
292
293 if (offset >= eod) {
294 return END_OF_STREAM;
295 }
296
297 int actualLen = eod - offset;
298 if (len < actualLen) {
299 actualLen = len;
300 }
301 if (actualLen <= 0) {
302 return 0;
303 }
304 System.arraycopy(data, offset, dest, off, actualLen);
305 offset += actualLen;
306 return actualLen;
307 }
308
309 @SuppressWarnings("sync-override")
310 @Override
311 public void reset() {
312 this.offset = this.markedOffset;
313 }
314
315 @Override
316 public long skip(final long n) {
317 if (n < 0) {
318 throw new IllegalArgumentException("Skipping backward is not supported");
319 }
320
321 long actualSkip = eod - offset;
322 if (n < actualSkip) {
323 actualSkip = n;
324 }
325
326 offset = Math.addExact(offset, Math.toIntExact(n));
327 return actualSkip;
328 }
329 }