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.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 }