001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.io.build;
019
020import java.io.File;
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.OutputStream;
024import java.io.RandomAccessFile;
025import java.io.Reader;
026import java.io.Writer;
027import java.nio.channels.Channel;
028import java.nio.channels.ReadableByteChannel;
029import java.nio.charset.Charset;
030import java.nio.file.OpenOption;
031import java.nio.file.Path;
032import java.util.function.IntUnaryOperator;
033
034import org.apache.commons.io.Charsets;
035import org.apache.commons.io.IOUtils;
036import org.apache.commons.io.file.PathUtils;
037
038/**
039 * 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,
040 * buffer size checker, a buffer size default, buffer size maximum, Charset, Charset default, default size checker, and open options. A subclass may use all,
041 * some, or none of these properties in building instances of {@code T}.
042 *
043 * @param <T> the type of instances to build.
044 * @param <B> the type of builder subclass.
045 * @since 2.12.0
046 */
047public abstract class AbstractStreamBuilder<T, B extends AbstractStreamBuilder<T, B>> extends AbstractOriginSupplier<T, B> {
048
049    private static final int DEFAULT_MAX_VALUE = Integer.MAX_VALUE;
050
051    private static final OpenOption[] DEFAULT_OPEN_OPTIONS = PathUtils.EMPTY_OPEN_OPTION_ARRAY;
052
053    /**
054     * The buffer size, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value IOUtils#DEFAULT_BUFFER_SIZE}).
055     */
056    private int bufferSize = IOUtils.DEFAULT_BUFFER_SIZE;
057
058    /**
059     * The buffer size, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value IOUtils#DEFAULT_BUFFER_SIZE}).
060     */
061    private int bufferSizeDefault = IOUtils.DEFAULT_BUFFER_SIZE;
062
063    /**
064     * The maximum buffer size.
065     */
066    private int bufferSizeMax = DEFAULT_MAX_VALUE;
067
068    /**
069     * The Charset, defaults to {@link Charset#defaultCharset()}.
070     */
071    private Charset charset = Charset.defaultCharset();
072
073    /**
074     * The Charset, defaults to {@link Charset#defaultCharset()}.
075     */
076    private Charset charsetDefault = Charset.defaultCharset();
077
078    private OpenOption[] openOptions = DEFAULT_OPEN_OPTIONS;
079
080    /**
081     * The default checking behavior for a buffer size request. Throws a {@link IllegalArgumentException} by default.
082     */
083    private final IntUnaryOperator defaultSizeChecker = size -> size > bufferSizeMax ? throwIae(size, bufferSizeMax) : size;
084
085    /**
086     * The checking behavior for a buffer size request.
087     */
088    private IntUnaryOperator bufferSizeChecker = defaultSizeChecker;
089
090    /**
091     * Constructs a new instance for subclasses.
092     */
093    public AbstractStreamBuilder() {
094        // empty
095    }
096
097    /**
098     * Applies the buffer size request.
099     *
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}