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  
18  package org.apache.commons.io.build;
19  
20  import java.io.File;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.io.RandomAccessFile;
25  import java.io.Reader;
26  import java.io.Writer;
27  import java.nio.channels.Channel;
28  import java.nio.channels.ReadableByteChannel;
29  import java.nio.charset.Charset;
30  import java.nio.file.OpenOption;
31  import java.nio.file.Path;
32  import java.util.function.IntUnaryOperator;
33  
34  import org.apache.commons.io.Charsets;
35  import org.apache.commons.io.IOUtils;
36  import org.apache.commons.io.file.PathUtils;
37  
38  /**
39   * Abstracts <em>building</em> a typed instance of type {@code T} where {@code T} is unbounded. This class contains various properties like a buffer size,
40   * buffer size checker, a buffer size default, buffer size maximum, Charset, Charset default, default size checker, and open options. A subclass may use all,
41   * some, or none of these properties in building instances of {@code T}.
42   *
43   * @param <T> the type of instances to build.
44   * @param <B> the type of builder subclass.
45   * @since 2.12.0
46   */
47  public abstract class AbstractStreamBuilder<T, B extends AbstractStreamBuilder<T, B>> extends AbstractOriginSupplier<T, B> {
48  
49      private static final int DEFAULT_MAX_VALUE = Integer.MAX_VALUE;
50  
51      private static final OpenOption[] DEFAULT_OPEN_OPTIONS = PathUtils.EMPTY_OPEN_OPTION_ARRAY;
52  
53      /**
54       * The buffer size, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value IOUtils#DEFAULT_BUFFER_SIZE}).
55       */
56      private int bufferSize = IOUtils.DEFAULT_BUFFER_SIZE;
57  
58      /**
59       * The buffer size, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value IOUtils#DEFAULT_BUFFER_SIZE}).
60       */
61      private int bufferSizeDefault = IOUtils.DEFAULT_BUFFER_SIZE;
62  
63      /**
64       * The maximum buffer size.
65       */
66      private int bufferSizeMax = DEFAULT_MAX_VALUE;
67  
68      /**
69       * The Charset, defaults to {@link Charset#defaultCharset()}.
70       */
71      private Charset charset = Charset.defaultCharset();
72  
73      /**
74       * The Charset, defaults to {@link Charset#defaultCharset()}.
75       */
76      private Charset charsetDefault = Charset.defaultCharset();
77  
78      private OpenOption[] openOptions = DEFAULT_OPEN_OPTIONS;
79  
80      /**
81       * The default checking behavior for a buffer size request. Throws a {@link IllegalArgumentException} by default.
82       */
83      private final IntUnaryOperator defaultSizeChecker = size -> size > bufferSizeMax ? throwIae(size, bufferSizeMax) : size;
84  
85      /**
86       * The checking behavior for a buffer size request.
87       */
88      private IntUnaryOperator bufferSizeChecker = defaultSizeChecker;
89  
90      /**
91       * Constructs a new instance for subclasses.
92       */
93      public AbstractStreamBuilder() {
94          // empty
95      }
96  
97      /**
98       * Applies the buffer size request.
99       *
100      * @param size the size request.
101      * @return the size to use, usually the input, or can throw an unchecked exception, like {@link IllegalArgumentException}.
102      */
103     private int checkBufferSize(final int size) {
104         return bufferSizeChecker.applyAsInt(size);
105     }
106 
107     /**
108      * Gets the buffer size, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value IOUtils#DEFAULT_BUFFER_SIZE}).
109      *
110      * @return the buffer size, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value IOUtils#DEFAULT_BUFFER_SIZE}).
111      */
112     public int getBufferSize() {
113         return bufferSize;
114     }
115 
116     /**
117      * Gets the buffer size default, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value IOUtils#DEFAULT_BUFFER_SIZE}).
118      *
119      * @return the buffer size default, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value IOUtils#DEFAULT_BUFFER_SIZE}).
120      */
121     public int getBufferSizeDefault() {
122         return bufferSizeDefault;
123     }
124 
125     /**
126      * Gets a byte array from the origin.
127      *
128      * @return A byte array.
129      * @throws IllegalStateException         if the {@code origin} is {@code null}.
130      * @throws UnsupportedOperationException if the origin cannot be converted to a byte array.
131      * @throws IOException                   if an I/O error occurs.
132      * @see AbstractOrigin#getByteArray()
133      * @since 2.22.0
134      */
135     public byte[] getByteArray() throws IOException {
136         return checkOrigin().getByteArray();
137     }
138 
139     /**
140      * Gets a Channel from the origin with OpenOption[].
141      *
142      * @param channelType The channel type, not null.
143      * @return A channel of the specified type.
144      * @param <C>         The channel type.
145      * @throws IllegalStateException         if the {@code origin} is {@code null}.
146      * @throws UnsupportedOperationException if the origin cannot be converted to a {@link ReadableByteChannel}.
147      * @throws IOException                   if an I/O error occurs.
148      * @see AbstractOrigin#getChannel
149      * @see #getOpenOptions()
150      * @since 2.21.0
151      */
152     public <C extends Channel> C getChannel(final Class<C> channelType) throws IOException {
153         return checkOrigin().getChannel(channelType, getOpenOptions());
154     }
155 
156     /**
157      * Gets a CharSequence from the origin with a Charset.
158      *
159      * @return An input stream.
160      * @throws IllegalStateException         if the {@code origin} is {@code null}.
161      * @throws UnsupportedOperationException if the origin cannot be converted to a CharSequence.
162      * @throws IOException                   if an I/O error occurs.
163      * @see AbstractOrigin#getCharSequence(Charset)
164      * @since 2.13.0
165      */
166     public CharSequence getCharSequence() throws IOException {
167         return checkOrigin().getCharSequence(getCharset());
168     }
169 
170     /**
171      * Gets the Charset, defaults to {@link Charset#defaultCharset()}.
172      *
173      * @return the Charset, defaults to {@link Charset#defaultCharset()}.
174      */
175     public Charset getCharset() {
176         return charset;
177     }
178 
179     /**
180      * Gets the Charset default, defaults to {@link Charset#defaultCharset()}.
181      *
182      * @return the Charset default, defaults to {@link Charset#defaultCharset()}.
183      */
184     public Charset getCharsetDefault() {
185         return charsetDefault;
186     }
187 
188     /**
189      * Gets a File from the origin.
190      *
191      * @return A File.
192      * @throws IllegalStateException         if the {@code origin} is {@code null}.
193      * @throws UnsupportedOperationException if the origin cannot be converted to a {@link File}.
194      * @see AbstractOrigin#getPath()
195      * @since 2.18.0
196      */
197     public File getFile() {
198         return checkOrigin().getFile();
199     }
200 
201     /**
202      * Gets an InputStream from the origin with OpenOption[].
203      *
204      * @return An input stream.
205      * @throws IllegalStateException         if the {@code origin} is {@code null}.
206      * @throws UnsupportedOperationException if the origin cannot be converted to an {@link InputStream}.
207      * @throws IOException                   if an I/O error occurs.
208      * @see AbstractOrigin#getInputStream(OpenOption...)
209      * @see #getOpenOptions()
210      * @since 2.13.0
211      */
212     public InputStream getInputStream() throws IOException {
213         return checkOrigin().getInputStream(getOpenOptions());
214     }
215 
216     /**
217      * Gets the OpenOption array.
218      *
219      * @return the OpenOption array, this is not a defensive copy, modify at your own risk.
220      */
221     public OpenOption[] getOpenOptions() {
222         return openOptions;
223     }
224 
225     /**
226      * Gets an OutputStream from the origin with OpenOption[].
227      *
228      * @return An OutputStream.
229      * @throws IllegalStateException         if the {@code origin} is {@code null}.
230      * @throws UnsupportedOperationException if the origin cannot be converted to an {@link OutputStream}.
231      * @throws IOException                   if an I/O error occurs.
232      * @see AbstractOrigin#getOutputStream(OpenOption...)
233      * @see #getOpenOptions()
234      * @since 2.13.0
235      */
236     public OutputStream getOutputStream() throws IOException {
237         return checkOrigin().getOutputStream(getOpenOptions());
238     }
239 
240     /**
241      * Gets a Path from the origin.
242      *
243      * @return A Path.
244      * @throws IllegalStateException         if the {@code origin} is {@code null}.
245      * @throws UnsupportedOperationException if the origin cannot be converted to a {@link Path}.
246      * @see AbstractOrigin#getPath()
247      * @since 2.13.0
248      */
249     public Path getPath() {
250         return checkOrigin().getPath();
251     }
252 
253     /**
254      * Gets a RandomAccessFile from the origin.
255      *
256      * @return A RandomAccessFile.
257      * @throws IllegalStateException         if the {@code origin} is {@code null}.
258      * @throws UnsupportedOperationException if the origin cannot be converted to a {@link RandomAccessFile}.
259      * @throws IOException                   if an I/O error occurs.
260      * @since 2.18.0
261      */
262     public RandomAccessFile getRandomAccessFile() throws IOException {
263         return checkOrigin().getRandomAccessFile(getOpenOptions());
264     }
265 
266     /**
267      * Gets a Reader from the origin with a Charset.
268      *
269      * @return A Reader.
270      * @throws IllegalStateException         if the {@code origin} is {@code null}.
271      * @throws UnsupportedOperationException if the origin cannot be converted to a {@link Reader}.
272      * @throws IOException                   if an I/O error occurs.
273      * @see AbstractOrigin#getReader(Charset)
274      * @see #getCharset()
275      * @since 2.16.0
276      */
277     public Reader getReader() throws IOException {
278         return checkOrigin().getReader(getCharset());
279     }
280 
281     /**
282      * Gets a Writer from the origin with an OpenOption[].
283      *
284      * @return An writer.
285      * @throws IllegalStateException         if the {@code origin} is {@code null}.
286      * @throws UnsupportedOperationException if the origin cannot be converted to a {@link Writer}.
287      * @throws IOException                   if an I/O error occurs.
288      * @see AbstractOrigin#getOutputStream(OpenOption...)
289      * @see #getOpenOptions()
290      * @since 2.13.0
291      */
292     public Writer getWriter() throws IOException {
293         return checkOrigin().getWriter(getCharset(), getOpenOptions());
294     }
295 
296     /**
297      * Sets the buffer size. Invalid input (bufferSize &lt;= 0) resets the value to its default.
298      * <p>
299      * Subclasses may ignore this setting.
300      * </p>
301      *
302      * @param bufferSize the buffer size, 0 resets to the default from {@link #getBufferSizeDefault()}.
303      * @return {@code this} instance.
304      */
305     public B setBufferSize(final int bufferSize) {
306         this.bufferSize = checkBufferSize(bufferSize > 0 ? bufferSize : bufferSizeDefault);
307         return asThis();
308     }
309 
310     /**
311      * Sets the buffer size.
312      * <p>
313      * Subclasses may ignore this setting.
314      * </p>
315      *
316      * @param bufferSize the buffer size, null resets to the default from {@link #getBufferSizeDefault()}.
317      * @return {@code this} instance.
318      */
319     public B setBufferSize(final Integer bufferSize) {
320         setBufferSize(bufferSize != null ? bufferSize : bufferSizeDefault);
321         return asThis();
322     }
323 
324     /**
325      * Sets the buffer size checker function. Throws a {@link IllegalArgumentException} by default.
326      *
327      * @param bufferSizeChecker the buffer size checker function. null resets to the default behavior.
328      * @return {@code this} instance.
329      * @since 2.14.0
330      */
331     public B setBufferSizeChecker(final IntUnaryOperator bufferSizeChecker) {
332         this.bufferSizeChecker = bufferSizeChecker != null ? bufferSizeChecker : defaultSizeChecker;
333         return asThis();
334     }
335 
336     /**
337      * Sets the buffer size for subclasses to initialize.
338      * <p>
339      * Subclasses may ignore this setting.
340      * </p>
341      *
342      * @param bufferSizeDefault the buffer size, 0 resets to the default {@link IOUtils#DEFAULT_BUFFER_SIZE}.
343      * @return {@code this} instance.
344      */
345     protected B setBufferSizeDefault(final int bufferSizeDefault) {
346         this.bufferSizeDefault = checkBufferSize(bufferSizeDefault > 0 ? bufferSizeDefault : IOUtils.DEFAULT_BUFFER_SIZE);
347         return asThis();
348     }
349 
350     /**
351      * The maximum buffer size checked by the buffer size checker. Values less or equal to 0, resets to the int max value. By default, if this value is
352      * exceeded, this methods throws an {@link IllegalArgumentException}.
353      *
354      * @param bufferSizeMax maximum buffer size checked by the buffer size checker.
355      * @return {@code this} instance.
356      * @since 2.14.0
357      */
358     public B setBufferSizeMax(final int bufferSizeMax) {
359         this.bufferSizeMax = bufferSizeMax > 0 ? bufferSizeMax : DEFAULT_MAX_VALUE;
360         return asThis();
361     }
362 
363     /**
364      * Sets the Charset.
365      * <p>
366      * Subclasses may ignore this setting.
367      * </p>
368      *
369      * @param charset the Charset, null resets to the default.
370      * @return {@code this} instance.
371      */
372     public B setCharset(final Charset charset) {
373         this.charset = Charsets.toCharset(charset, charsetDefault);
374         return asThis();
375     }
376 
377     /**
378      * Sets the Charset.
379      * <p>
380      * Subclasses may ignore this setting.
381      * </p>
382      *
383      * @param charset the Charset name, null resets to the default.
384      * @return {@code this} instance.
385      */
386     public B setCharset(final String charset) {
387         return setCharset(Charsets.toCharset(charset, charsetDefault));
388     }
389 
390     /**
391      * Sets the Charset default for subclasses to initialize.
392      * <p>
393      * Subclasses may ignore this setting.
394      * </p>
395      *
396      * @param defaultCharset the Charset name, null resets to the default.
397      * @return {@code this} instance.
398      */
399     protected B setCharsetDefault(final Charset defaultCharset) {
400         this.charsetDefault = defaultCharset;
401         return asThis();
402     }
403 
404     /**
405      * Sets the OpenOption array.
406      * <p>
407      * Normally used with InputStream, OutputStream, and Writer.
408      * </p>
409      * <p>
410      * Subclasses may ignore this setting.
411      * </p>
412      *
413      * @param openOptions the OpenOption[] name, null resets to the default, a defensive copy is made.
414      * @return {@code this} instance.
415      * @since 2.13.0
416      * @see #setInputStream(InputStream)
417      * @see #setOutputStream(OutputStream)
418      * @see #setWriter(Writer)
419      */
420     public B setOpenOptions(final OpenOption... openOptions) {
421         this.openOptions = openOptions != null ? openOptions.clone() : DEFAULT_OPEN_OPTIONS;
422         return asThis();
423     }
424 
425     private int throwIae(final int size, final int max) {
426         throw new IllegalArgumentException(String.format("Request %,d exceeds maximum %,d", size, max));
427     }
428 }