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}