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    *      http://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          /**
95           * Builds a new {@link UnsynchronizedByteArrayInputStream}.
96           * <p>
97           * You must set an aspect that supports {@code byte[]} on this builder, otherwise, this method throws an exception.
98           * </p>
99           * <p>
100          * This builder uses the following aspects:
101          * </p>
102          * <ul>
103          * <li>{@code byte[]}</li>
104          * <li>offset</li>
105          * <li>length</li>
106          * </ul>
107          *
108          * @return a new instance.
109          * @throws UnsupportedOperationException if the origin cannot provide a {@code byte[]}.
110          * @throws IllegalStateException         if the {@code origin} is {@code null}.
111          * @throws IOException                   if an I/O error occurs converting to an {@code byte[]} using {@link AbstractOrigin#getByteArray()}.
112          * @see AbstractOrigin#getByteArray()
113          * @see #getUnchecked()
114          */
115         @Override
116         public UnsynchronizedByteArrayInputStream get() throws IOException {
117             return new UnsynchronizedByteArrayInputStream(checkOrigin().getByteArray(), offset, length);
118         }
119 
120         @Override
121         public Builder setByteArray(final byte[] origin) {
122             length = Objects.requireNonNull(origin, "origin").length;
123             return super.setByteArray(origin);
124         }
125 
126         /**
127          * Sets the length.
128          *
129          * @param length Must be greater or equal to 0.
130          * @return {@code this} instance.
131          */
132         public Builder setLength(final int length) {
133             if (length < 0) {
134                 throw new IllegalArgumentException("length cannot be negative");
135             }
136             this.length = length;
137             return this;
138         }
139 
140         /**
141          * Sets the offset.
142          *
143          * @param offset Must be greater or equal to 0.
144          * @return {@code this} instance.
145          */
146         public Builder setOffset(final int offset) {
147             if (offset < 0) {
148                 throw new IllegalArgumentException("offset cannot be negative");
149             }
150             this.offset = offset;
151             return this;
152         }
153 
154     }
155 
156     /**
157      * The end of stream marker.
158      */
159     public static final int END_OF_STREAM = -1;
160 
161     /**
162      * Constructs a new {@link Builder}.
163      *
164      * @return a new {@link Builder}.
165      */
166     public static Builder builder() {
167         return new Builder();
168     }
169 
170     private static int minPosLen(final byte[] data, final int defaultValue) {
171         requireNonNegative(defaultValue, "defaultValue");
172         return Math.min(defaultValue, data.length > 0 ? data.length : defaultValue);
173     }
174 
175     private static int requireNonNegative(final int value, final String name) {
176         if (value < 0) {
177             throw new IllegalArgumentException(name + " cannot be negative");
178         }
179         return value;
180     }
181 
182     /**
183      * The underlying data buffer.
184      */
185     private final byte[] data;
186 
187     /**
188      * End Of Data.
189      *
190      * Similar to data.length, which is the last readable offset + 1.
191      */
192     private final int eod;
193 
194     /**
195      * Current offset in the data buffer.
196      */
197     private int offset;
198 
199     /**
200      * The current mark (if any).
201      */
202     private int markedOffset;
203 
204     /**
205      * Constructs a new byte array input stream.
206      *
207      * @param data the buffer
208      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
209      */
210     @Deprecated
211     public UnsynchronizedByteArrayInputStream(final byte[] data) {
212         this(data, data.length, 0, 0);
213     }
214 
215     /**
216      * Constructs a new byte array input stream.
217      *
218      * @param data   the buffer
219      * @param offset the offset into the buffer
220      * @throws IllegalArgumentException if the offset is less than zero
221      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
222      */
223     @Deprecated
224     public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset) {
225         this(data, data.length, Math.min(requireNonNegative(offset, "offset"), minPosLen(data, offset)), minPosLen(data, offset));
226     }
227 
228     /**
229      * Constructs a new byte array input stream.
230      *
231      * @param data   the buffer
232      * @param offset the offset into the buffer
233      * @param length the length of the buffer
234      * @throws IllegalArgumentException if the offset or length less than zero
235      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
236      */
237     @Deprecated
238     public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset, final int length) {
239         requireNonNegative(offset, "offset");
240         requireNonNegative(length, "length");
241         this.data = Objects.requireNonNull(data, "data");
242         this.eod = Math.min(minPosLen(data, offset) + length, data.length);
243         this.offset = minPosLen(data, offset);
244         this.markedOffset = minPosLen(data, offset);
245     }
246 
247     private UnsynchronizedByteArrayInputStream(final byte[] data, final int eod, final int offset, final int markedOffset) {
248         this.data = Objects.requireNonNull(data, "data");
249         this.eod = eod;
250         this.offset = offset;
251         this.markedOffset = markedOffset;
252     }
253 
254     @Override
255     public int available() {
256         return offset < eod ? eod - offset : 0;
257     }
258 
259     @SuppressWarnings("sync-override")
260     @Override
261     public void mark(final int readLimit) {
262         this.markedOffset = this.offset;
263     }
264 
265     @Override
266     public boolean markSupported() {
267         return true;
268     }
269 
270     @Override
271     public int read() {
272         return offset < eod ? data[offset++] & 0xff : END_OF_STREAM;
273     }
274 
275     @Override
276     public int read(final byte[] dest) {
277         Objects.requireNonNull(dest, "dest");
278         return read(dest, 0, dest.length);
279     }
280 
281     @Override
282     public int read(final byte[] dest, final int off, final int len) {
283         Objects.requireNonNull(dest, "dest");
284         if (off < 0 || len < 0 || off + len > dest.length) {
285             throw new IndexOutOfBoundsException();
286         }
287 
288         if (offset >= eod) {
289             return END_OF_STREAM;
290         }
291 
292         int actualLen = eod - offset;
293         if (len < actualLen) {
294             actualLen = len;
295         }
296         if (actualLen <= 0) {
297             return 0;
298         }
299         System.arraycopy(data, offset, dest, off, actualLen);
300         offset += actualLen;
301         return actualLen;
302     }
303 
304     @SuppressWarnings("sync-override")
305     @Override
306     public void reset() {
307         this.offset = this.markedOffset;
308     }
309 
310     @Override
311     public long skip(final long n) {
312         if (n < 0) {
313             throw new IllegalArgumentException("Skipping backward is not supported");
314         }
315 
316         long actualSkip = eod - offset;
317         if (n < actualSkip) {
318             actualSkip = n;
319         }
320 
321         offset = Math.addExact(offset, Math.toIntExact(n));
322         return actualSkip;
323     }
324 }