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 Channel from the origin with OpenOption[].
127      *
128      * @param channelType The channel type, not null.
129      * @return A channel of the specified type.
130      * @param <C>         The channel type.
131      * @throws IllegalStateException         if the {@code origin} is {@code null}.
132      * @throws UnsupportedOperationException if the origin cannot be converted to a {@link ReadableByteChannel}.
133      * @throws IOException                   if an I/O error occurs.
134      * @see AbstractOrigin#getChannel
135      * @see #getOpenOptions()
136      * @since 2.21.0
137      */
138     public <C extends Channel> C getChannel(final Class<C> channelType) throws IOException {
139         return checkOrigin().getChannel(channelType, getOpenOptions());
140     }
141 
142     /**
143      * Gets a CharSequence from the origin with a Charset.
144      *
145      * @return An input stream
146      * @throws IllegalStateException         if the {@code origin} is {@code null}.
147      * @throws UnsupportedOperationException if the origin cannot be converted to a CharSequence.
148      * @throws IOException                   if an I/O error occurs.
149      * @see AbstractOrigin#getCharSequence(Charset)
150      * @since 2.13.0
151      */
152     public CharSequence getCharSequence() throws IOException {
153         return checkOrigin().getCharSequence(getCharset());
154     }
155 
156     /**
157      * Gets the Charset, defaults to {@link Charset#defaultCharset()}.
158      *
159      * @return the Charset, defaults to {@link Charset#defaultCharset()}.
160      */
161     public Charset getCharset() {
162         return charset;
163     }
164 
165     /**
166      * Gets the Charset default, defaults to {@link Charset#defaultCharset()}.
167      *
168      * @return the Charset default, defaults to {@link Charset#defaultCharset()}.
169      */
170     public Charset getCharsetDefault() {
171         return charsetDefault;
172     }
173 
174     /**
175      * Gets a File from the origin.
176      *
177      * @return A File
178      * @throws IllegalStateException         if the {@code origin} is {@code null}.
179      * @throws UnsupportedOperationException if the origin cannot be converted to a {@link File}.
180      * @see AbstractOrigin#getPath()
181      * @since 2.18.0
182      */
183     public File getFile() {
184         return checkOrigin().getFile();
185     }
186 
187     /**
188      * Gets an InputStream from the origin with OpenOption[].
189      *
190      * @return An input stream
191      * @throws IllegalStateException         if the {@code origin} is {@code null}.
192      * @throws UnsupportedOperationException if the origin cannot be converted to an {@link InputStream}.
193      * @throws IOException                   if an I/O error occurs.
194      * @see AbstractOrigin#getInputStream(OpenOption...)
195      * @see #getOpenOptions()
196      * @since 2.13.0
197      */
198     public InputStream getInputStream() throws IOException {
199         return checkOrigin().getInputStream(getOpenOptions());
200     }
201 
202     /**
203      * Gets the OpenOption array.
204      *
205      * @return the OpenOption array.
206      */
207     public OpenOption[] getOpenOptions() {
208         return openOptions;
209     }
210 
211     /**
212      * Gets an OutputStream from the origin with OpenOption[].
213      *
214      * @return An OutputStream
215      * @throws IllegalStateException         if the {@code origin} is {@code null}.
216      * @throws UnsupportedOperationException if the origin cannot be converted to an {@link OutputStream}.
217      * @throws IOException                   if an I/O error occurs.
218      * @see AbstractOrigin#getOutputStream(OpenOption...)
219      * @see #getOpenOptions()
220      * @since 2.13.0
221      */
222     public OutputStream getOutputStream() throws IOException {
223         return checkOrigin().getOutputStream(getOpenOptions());
224     }
225 
226     /**
227      * Gets a Path from the origin.
228      *
229      * @return A Path
230      * @throws IllegalStateException         if the {@code origin} is {@code null}.
231      * @throws UnsupportedOperationException if the origin cannot be converted to a {@link Path}.
232      * @see AbstractOrigin#getPath()
233      * @since 2.13.0
234      */
235     public Path getPath() {
236         return checkOrigin().getPath();
237     }
238 
239     /**
240      * Gets a RandomAccessFile from the origin.
241      *
242      * @return A RandomAccessFile
243      * @throws IllegalStateException         if the {@code origin} is {@code null}.
244      * @throws UnsupportedOperationException if the origin cannot be converted to a {@link RandomAccessFile}.
245      * @throws IOException                   if an I/O error occurs.
246      * @since 2.18.0
247      */
248     public RandomAccessFile getRandomAccessFile() throws IOException {
249         return checkOrigin().getRandomAccessFile(getOpenOptions());
250     }
251 
252     /**
253      * Gets a Reader from the origin with a Charset.
254      *
255      * @return A Reader
256      * @throws IllegalStateException         if the {@code origin} is {@code null}.
257      * @throws UnsupportedOperationException if the origin cannot be converted to a {@link Reader}.
258      * @throws IOException                   if an I/O error occurs.
259      * @see AbstractOrigin#getReader(Charset)
260      * @see #getCharset()
261      * @since 2.16.0
262      */
263     public Reader getReader() throws IOException {
264         return checkOrigin().getReader(getCharset());
265     }
266 
267     /**
268      * Gets a Writer from the origin with an OpenOption[].
269      *
270      * @return An writer.
271      * @throws IllegalStateException         if the {@code origin} is {@code null}.
272      * @throws UnsupportedOperationException if the origin cannot be converted to a {@link Writer}.
273      * @throws IOException                   if an I/O error occurs.
274      * @see AbstractOrigin#getOutputStream(OpenOption...)
275      * @see #getOpenOptions()
276      * @since 2.13.0
277      */
278     public Writer getWriter() throws IOException {
279         return checkOrigin().getWriter(getCharset(), getOpenOptions());
280     }
281 
282     /**
283      * Sets the buffer size. Invalid input (bufferSize &lt;= 0) resets the value to its default.
284      * <p>
285      * Subclasses may ignore this setting.
286      * </p>
287      *
288      * @param bufferSize the buffer size.
289      * @return {@code this} instance.
290      */
291     public B setBufferSize(final int bufferSize) {
292         this.bufferSize = checkBufferSize(bufferSize > 0 ? bufferSize : bufferSizeDefault);
293         return asThis();
294     }
295 
296     /**
297      * Sets the buffer size.
298      * <p>
299      * Subclasses may ignore this setting.
300      * </p>
301      *
302      * @param bufferSize the buffer size, null resets to the default.
303      * @return {@code this} instance.
304      */
305     public B setBufferSize(final Integer bufferSize) {
306         setBufferSize(bufferSize != null ? bufferSize : bufferSizeDefault);
307         return asThis();
308     }
309 
310     /**
311      * Sets the buffer size checker function. Throws a {@link IllegalArgumentException} by default.
312      *
313      * @param bufferSizeChecker the buffer size checker function. null resets to the default behavior.
314      * @return {@code this} instance.
315      * @since 2.14.0
316      */
317     public B setBufferSizeChecker(final IntUnaryOperator bufferSizeChecker) {
318         this.bufferSizeChecker = bufferSizeChecker != null ? bufferSizeChecker : defaultSizeChecker;
319         return asThis();
320     }
321 
322     /**
323      * Sets the buffer size for subclasses to initialize.
324      * <p>
325      * Subclasses may ignore this setting.
326      * </p>
327      *
328      * @param bufferSizeDefault the buffer size, null resets to the default.
329      * @return {@code this} instance.
330      */
331     protected B setBufferSizeDefault(final int bufferSizeDefault) {
332         this.bufferSizeDefault = bufferSizeDefault;
333         return asThis();
334     }
335 
336     /**
337      * 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
338      * exceeded, this methods throws an {@link IllegalArgumentException}.
339      *
340      * @param bufferSizeMax maximum buffer size checked by the buffer size checker.
341      * @return {@code this} instance.
342      * @since 2.14.0
343      */
344     public B setBufferSizeMax(final int bufferSizeMax) {
345         this.bufferSizeMax = bufferSizeMax > 0 ? bufferSizeMax : DEFAULT_MAX_VALUE;
346         return asThis();
347     }
348 
349     /**
350      * Sets the Charset.
351      * <p>
352      * Subclasses may ignore this setting.
353      * </p>
354      *
355      * @param charset the Charset, null resets to the default.
356      * @return {@code this} instance.
357      */
358     public B setCharset(final Charset charset) {
359         this.charset = Charsets.toCharset(charset, charsetDefault);
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 name, null resets to the default.
370      * @return {@code this} instance.
371      */
372     public B setCharset(final String charset) {
373         return setCharset(Charsets.toCharset(charset, charsetDefault));
374     }
375 
376     /**
377      * Sets the Charset default for subclasses to initialize.
378      * <p>
379      * Subclasses may ignore this setting.
380      * </p>
381      *
382      * @param defaultCharset the Charset name, null resets to the default.
383      * @return {@code this} instance.
384      */
385     protected B setCharsetDefault(final Charset defaultCharset) {
386         this.charsetDefault = defaultCharset;
387         return asThis();
388     }
389 
390     /**
391      * Sets the OpenOption[].
392      * <p>
393      * Normally used with InputStream, OutputStream, and Writer.
394      * </p>
395      * <p>
396      * Subclasses may ignore this setting.
397      * </p>
398      *
399      * @param openOptions the OpenOption[] name, null resets to the default.
400      * @return {@code this} instance.
401      * @since 2.13.0
402      * @see #setInputStream(InputStream)
403      * @see #setOutputStream(OutputStream)
404      * @see #setWriter(Writer)
405      */
406     public B setOpenOptions(final OpenOption... openOptions) {
407         this.openOptions = openOptions != null ? openOptions : DEFAULT_OPEN_OPTIONS;
408         return asThis();
409     }
410 
411     private int throwIae(final int size, final int max) {
412         throw new IllegalArgumentException(String.format("Request %,d exceeds maximum %,d", size, max));
413     }
414 }