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 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}