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 private byte[] checkOriginByteArray() throws IOException {
96 return checkOrigin().getByteArray();
97 }
98
99 /**
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 }