    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 *      http://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.charset.Charset;
    028import java.nio.file.OpenOption;
    029import java.nio.file.Path;
    030import java.util.function.IntUnaryOperator;
    031
    032import org.apache.commons.io.Charsets;
    033import org.apache.commons.io.IOUtils;
    034import org.apache.commons.io.file.PathUtils;
    035
    036/**
    037 * Abstracts building a typed instance of {@code T}.
    038 *
    039 * @param <T> the type of instances to build.
    040 * @param <B> the type of builder subclass.
    041 * @since 2.12.0
    042 */
    043public abstract class AbstractStreamBuilder<T, B extends AbstractStreamBuilder<T, B>> extends AbstractOriginSupplier<T, B> {
    044
    045    private static final int DEFAULT_MAX_VALUE = Integer.MAX_VALUE;
    046
    047    private static final OpenOption[] DEFAULT_OPEN_OPTIONS = PathUtils.EMPTY_OPEN_OPTION_ARRAY;
    048
    049    /**
    050     * The buffer size, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value IOUtils#DEFAULT_BUFFER_SIZE}).
    051     */
    052    private int bufferSize = IOUtils.DEFAULT_BUFFER_SIZE;
    053
    054    /**
    055     * The buffer size, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value IOUtils#DEFAULT_BUFFER_SIZE}).
    056     */
    057    private int bufferSizeDefault = IOUtils.DEFAULT_BUFFER_SIZE;
    058
    059    /**
    060     * The maximum buffer size.
    061     */
    062    private int bufferSizeMax = DEFAULT_MAX_VALUE;
    063
    064    /**
    065     * The Charset, defaults to {@link Charset#defaultCharset()}.
    066     */
    067    private Charset charset = Charset.defaultCharset();
    068
    069    /**
    070     * The Charset, defaults to {@link Charset#defaultCharset()}.
    071     */
    072    private Charset charsetDefault = Charset.defaultCharset();
    073
    074    private OpenOption[] openOptions = DEFAULT_OPEN_OPTIONS;
    075
    076    /**
    077     * The default checking behavior for a buffer size request. Throws a {@link IllegalArgumentException} by default.
    078     */
    079    private final IntUnaryOperator defaultSizeChecker = size -> size > bufferSizeMax ? throwIae(size, bufferSizeMax) : size;
    080
    081    /**
    082     * The checking behavior for a buffer size request.
    083     */
    084    private IntUnaryOperator bufferSizeChecker = defaultSizeChecker;
    085
    086    /**
    087     * Constructs a new instance for subclasses.
    088     */
    089    public AbstractStreamBuilder() {
    090        // empty
    091    }
    092
    093    /**
    094     * Applies the buffer size request.
    095     *
    096     * @param size the size request.
    097     * @return the size to use, usually the input, or can throw an unchecked exception, like {@link IllegalArgumentException}.
    098     */
    099    private int checkBufferSize(final int size) {
    100        return bufferSizeChecker.applyAsInt(size);
    101    }
    102
    103    /**
    104     * Gets the buffer size, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value IOUtils#DEFAULT_BUFFER_SIZE}).
    105     *
    106     * @return the buffer size, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value IOUtils#DEFAULT_BUFFER_SIZE}).
    107     */
    108    public int getBufferSize() {
    109        return bufferSize;
    110    }
    111
    112    /**
    113     * Gets the buffer size default, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value IOUtils#DEFAULT_BUFFER_SIZE}).
    114     *
    115     * @return the buffer size default, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value IOUtils#DEFAULT_BUFFER_SIZE}).
    116     */
    117    public int getBufferSizeDefault() {
    118        return bufferSizeDefault;
    119    }
    120
    121    /**
    122     * Gets a CharSequence from the origin with a Charset.
    123     *
    124     * @return An input stream
    125     * @throws IllegalStateException         if the {@code origin} is {@code null}.
    126     * @throws UnsupportedOperationException if the origin cannot be converted to a CharSequence.
    127     * @throws IOException                   if an I/O error occurs.
    128     * @see AbstractOrigin#getCharSequence(Charset)
    129     * @since 2.13.0
    130     */
    131    public CharSequence getCharSequence() throws IOException {
    132        return checkOrigin().getCharSequence(getCharset());
    133    }
    134
    135    /**
    136     * Gets the Charset, defaults to {@link Charset#defaultCharset()}.
    137     *
    138     * @return the Charset, defaults to {@link Charset#defaultCharset()}.
    139     */
    140    public Charset getCharset() {
    141        return charset;
    142    }
    143
    144    /**
    145     * Gets the Charset default, defaults to {@link Charset#defaultCharset()}.
    146     *
    147     * @return the Charset default, defaults to {@link Charset#defaultCharset()}.
    148     */
    149    public Charset getCharsetDefault() {
    150        return charsetDefault;
    151    }
    152
    153    /**
    154     * Gets a File from the origin.
    155     *
    156     * @return A File
    157     * @throws IllegalStateException         if the {@code origin} is {@code null}.
    158     * @throws UnsupportedOperationException if the origin cannot be converted to a {@link File}.
    159     * @see AbstractOrigin#getPath()
    160     * @since 2.18.0
    161     */
    162    public File getFile() {
    163        return checkOrigin().getFile();
    164    }
    165
    166    /**
    167     * Gets an InputStream from the origin with OpenOption[].
    168     *
    169     * @return An input stream
    170     * @throws IllegalStateException         if the {@code origin} is {@code null}.
    171     * @throws UnsupportedOperationException if the origin cannot be converted to an {@link InputStream}.
    172     * @throws IOException                   if an I/O error occurs.
    173     * @see AbstractOrigin#getInputStream(OpenOption...)
    174     * @see #getOpenOptions()
    175     * @since 2.13.0
    176     */
    177    public InputStream getInputStream() throws IOException {
    178        return checkOrigin().getInputStream(getOpenOptions());
    179    }
    180
    181    /**
    182     * Gets the OpenOption array.
    183     *
    184     * @return the OpenOption array.
    185     */
    186    public OpenOption[] getOpenOptions() {
    187        return openOptions;
    188    }
    189
    190    /**
    191     * Gets an OutputStream from the origin with OpenOption[].
    192     *
    193     * @return An OutputStream
    194     * @throws IllegalStateException         if the {@code origin} is {@code null}.
    195     * @throws UnsupportedOperationException if the origin cannot be converted to an {@link OutputStream}.
    196     * @throws IOException                   if an I/O error occurs.
    197     * @see AbstractOrigin#getOutputStream(OpenOption...)
    198     * @see #getOpenOptions()
    199     * @since 2.13.0
    200     */
    201    public OutputStream getOutputStream() throws IOException {
    202        return checkOrigin().getOutputStream(getOpenOptions());
    203    }
    204
    205    /**
    206     * Gets a Path from the origin.
    207     *
    208     * @return A Path
    209     * @throws IllegalStateException         if the {@code origin} is {@code null}.
    210     * @throws UnsupportedOperationException if the origin cannot be converted to a {@link Path}.
    211     * @see AbstractOrigin#getPath()
    212     * @since 2.13.0
    213     */
    214    public Path getPath() {
    215        return checkOrigin().getPath();
    216    }
    217
    218    /**
    219     * Gets a RandomAccessFile from the origin.
    220     *
    221     * @return A RandomAccessFile
    222     * @throws IllegalStateException         if the {@code origin} is {@code null}.
    223     * @throws UnsupportedOperationException if the origin cannot be converted to a {@link RandomAccessFile}.
    224     * @throws IOException                   if an I/O error occurs.
    225     * @since 2.18.0
    226     */
    227    public RandomAccessFile getRandomAccessFile() throws IOException {
    228        return checkOrigin().getRandomAccessFile(getOpenOptions());
    229    }
    230
    231    /**
    232     * Gets a Reader from the origin with a Charset.
    233     *
    234     * @return A Reader
    235     * @throws IllegalStateException         if the {@code origin} is {@code null}.
    236     * @throws UnsupportedOperationException if the origin cannot be converted to a {@link Reader}.
    237     * @throws IOException                   if an I/O error occurs.
    238     * @see AbstractOrigin#getReader(Charset)
    239     * @see #getCharset()
    240     * @since 2.16.0
    241     */
    242    public Reader getReader() throws IOException {
    243        return checkOrigin().getReader(getCharset());
    244    }
    245
    246    /**
    247     * Gets a Writer from the origin with an OpenOption[].
    248     *
    249     * @return An writer.
    250     * @throws IllegalStateException         if the {@code origin} is {@code null}.
    251     * @throws UnsupportedOperationException if the origin cannot be converted to a {@link Writer}.
    252     * @throws IOException                   if an I/O error occurs.
    253     * @see AbstractOrigin#getOutputStream(OpenOption...)
    254     * @see #getOpenOptions()
    255     * @since 2.13.0
    256     */
    257    public Writer getWriter() throws IOException {
    258        return checkOrigin().getWriter(getCharset(), getOpenOptions());
    259    }
    260
    261    /**
    262     * Sets the buffer size. Invalid input (bufferSize &lt;= 0) resets the value to its default.
    263     * <p>
    264     * Subclasses may ignore this setting.
    265     * </p>
    266     *
    267     * @param bufferSize the buffer size.
    268     * @return {@code this} instance.
    269     */
    270    public B setBufferSize(final int bufferSize) {
    271        this.bufferSize = checkBufferSize(bufferSize > 0 ? bufferSize : bufferSizeDefault);
    272        return asThis();
    273    }
    274
    275    /**
    276     * Sets the buffer size.
    277     * <p>
    278     * Subclasses may ignore this setting.
    279     * </p>
    280     *
    281     * @param bufferSize the buffer size, null resets to the default.
    282     * @return {@code this} instance.
    283     */
    284    public B setBufferSize(final Integer bufferSize) {
    285        setBufferSize(bufferSize != null ? bufferSize : bufferSizeDefault);
    286        return asThis();
    287    }
    288
    289    /**
    290     * Sets the buffer size checker function. Throws a {@link IllegalArgumentException} by default.
    291     *
    292     * @param bufferSizeChecker the buffer size checker function. null resets to the default behavior.
    293     * @return {@code this} instance.
    294     * @since 2.14.0
    295     */
    296    public B setBufferSizeChecker(final IntUnaryOperator bufferSizeChecker) {
    297        this.bufferSizeChecker = bufferSizeChecker != null ? bufferSizeChecker : defaultSizeChecker;
    298        return asThis();
    299    }
    300
    301    /**
    302     * Sets the buffer size for subclasses to initialize.
    303     * <p>
    304     * Subclasses may ignore this setting.
    305     * </p>
    306     *
    307     * @param bufferSizeDefault the buffer size, null resets to the default.
    308     * @return {@code this} instance.
    309     */
    310    protected B setBufferSizeDefault(final int bufferSizeDefault) {
    311        this.bufferSizeDefault = bufferSizeDefault;
    312        return asThis();
    313    }
    314
    315    /**
    316     * 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
    317     * exceeded, this methods throws an {@link IllegalArgumentException}.
    318     *
    319     * @param bufferSizeMax maximum buffer size checked by the buffer size checker.
    320     * @return {@code this} instance.
    321     * @since 2.14.0
    322     */
    323    public B setBufferSizeMax(final int bufferSizeMax) {
    324        this.bufferSizeMax = bufferSizeMax > 0 ? bufferSizeMax : DEFAULT_MAX_VALUE;
    325        return asThis();
    326    }
    327
    328    /**
    329     * Sets the Charset.
    330     * <p>
    331     * Subclasses may ignore this setting.
    332     * </p>
    333     *
    334     * @param charset the Charset, null resets to the default.
    335     * @return {@code this} instance.
    336     */
    337    public B setCharset(final Charset charset) {
    338        this.charset = Charsets.toCharset(charset, charsetDefault);
    339        return asThis();
    340    }
    341
    342    /**
    343     * Sets the Charset.
    344     * <p>
    345     * Subclasses may ignore this setting.
    346     * </p>
    347     *
    348     * @param charset the Charset name, null resets to the default.
    349     * @return {@code this} instance.
    350     */
    351    public B setCharset(final String charset) {
    352        return setCharset(Charsets.toCharset(charset, charsetDefault));
    353    }
    354
    355    /**
    356     * Sets the Charset default for subclasses to initialize.
    357     * <p>
    358     * Subclasses may ignore this setting.
    359     * </p>
    360     *
    361     * @param defaultCharset the Charset name, null resets to the default.
    362     * @return {@code this} instance.
    363     */
    364    protected B setCharsetDefault(final Charset defaultCharset) {
    365        this.charsetDefault = defaultCharset;
    366        return asThis();
    367    }
    368
    369    /**
    370     * Sets the OpenOption[].
    371     * <p>
    372     * Normally used with InputStream, OutputStream, and Writer.
    373     * </p>
    374     * <p>
    375     * Subclasses may ignore this setting.
    376     * </p>
    377     *
    378     * @param openOptions the OpenOption[] name, null resets to the default.
    379     * @return {@code this} instance.
    380     * @since 2.13.0
    381     * @see #setInputStream(InputStream)
    382     * @see #setOutputStream(OutputStream)
    383     * @see #setWriter(Writer)
    384     */
    385    public B setOpenOptions(final OpenOption... openOptions) {
    386        this.openOptions = openOptions != null ? openOptions : DEFAULT_OPEN_OPTIONS;
    387        return asThis();
    388    }
    389
    390    private int throwIae(final int size, final int max) {
    391        throw new IllegalArgumentException(String.format("Request %,d exceeds maximum %,d", size, max));
    392    }
    393}