    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}