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