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