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