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.Reader;
025import java.io.Writer;
026import java.net.URI;
027import java.nio.charset.Charset;
028import java.nio.file.Files;
029import java.nio.file.OpenOption;
030import java.nio.file.Path;
031import java.nio.file.Paths;
032import java.util.Objects;
033
034/**
035 * Abstracts the origin of data for builders like a {@link File}, {@link Path}, {@link Reader}, {@link Writer}, {@link InputStream}, {@link OutputStream}, and
036 * {@link URI}.
037 * <p>
038 * Some methods may throw {@link UnsupportedOperationException} if that method is not implemented in a concrete subclass, see {@link #getFile()} and
039 * {@link #getPath()}.
040 * </p>
041 *
042 * @param <T> the type of instances to build.
043 * @param <B> the type of builder subclass.
044 * @since 2.12.0
045 */
046public abstract class AbstractOrigin<T, B extends AbstractOrigin<T, B>> extends AbstractSupplier<T, B> {
047
048    /**
049     * A {@link File} origin.
050     */
051    public static class ByteArrayOrigin extends AbstractOrigin<byte[], ByteArrayOrigin> {
052
053        /**
054         * Constructs a new instance for the given origin.
055         *
056         * @param origin The origin.
057         */
058        public ByteArrayOrigin(final byte[] origin) {
059            super(origin);
060        }
061
062        @Override
063        public byte[] getByteArray() {
064            // No conversion
065            return get();
066        }
067
068    }
069
070    /**
071     * A {@link File} origin.
072     * <p>
073     * Starting from this origin, you can get a byte array, a file, an input stream, an output stream, a path, a reader, and a writer.
074     * </p>
075     */
076    public static class FileOrigin extends AbstractOrigin<File, FileOrigin> {
077
078        /**
079         * Constructs a new instance for the given origin.
080         *
081         * @param origin The origin.
082         */
083        public FileOrigin(final File origin) {
084            super(origin);
085        }
086
087        @Override
088        public File getFile() {
089            // No conversion
090            return get();
091        }
092
093        @Override
094        public Path getPath() {
095            return get().toPath();
096        }
097
098    }
099
100    /**
101     * An {@link InputStream} origin.
102     * <p>
103     * This origin cannot provide other aspects.
104     * </p>
105     */
106    public static class InputStreamOrigin extends AbstractOrigin<InputStream, InputStreamOrigin> {
107
108        /**
109         * Constructs a new instance for the given origin.
110         *
111         * @param origin The origin.
112         */
113        public InputStreamOrigin(final InputStream origin) {
114            super(origin);
115        }
116
117        @Override
118        public InputStream getInputStream(final OpenOption... options) {
119            // No conversion
120            return get();
121        }
122
123    }
124
125    /**
126     * An {@link OutputStream} origin.
127     * <p>
128     * This origin cannot provide other aspects.
129     * </p>
130     */
131    public static class OutputStreamOrigin extends AbstractOrigin<OutputStream, OutputStreamOrigin> {
132
133        /**
134         * Constructs a new instance for the given origin.
135         *
136         * @param origin The origin.
137         */
138        public OutputStreamOrigin(final OutputStream origin) {
139            super(origin);
140        }
141
142        @Override
143        public OutputStream getOutputStream(final OpenOption... options) {
144            // No conversion
145            return get();
146        }
147
148    }
149
150    /**
151     * A {@link Path} origin.
152     * <p>
153     * Starting from this origin, you can get a byte array, a file, an input stream, an output stream, a path, a reader, and a writer.
154     * </p>
155     */
156    public static class PathOrigin extends AbstractOrigin<Path, PathOrigin> {
157
158        /**
159         * Constructs a new instance for the given origin.
160         *
161         * @param origin The origin.
162         */
163        public PathOrigin(final Path origin) {
164            super(origin);
165        }
166
167        @Override
168        public File getFile() {
169            return get().toFile();
170        }
171
172        @Override
173        public Path getPath() {
174            // No conversion
175            return get();
176        }
177
178    }
179
180    /**
181     * An {@link Reader} origin.
182     * <p>
183     * This origin cannot provide other aspects.
184     * </p>
185     */
186    public static class ReaderOrigin extends AbstractOrigin<Reader, ReaderOrigin> {
187
188        /**
189         * Constructs a new instance for the given origin.
190         *
191         * @param origin The origin.
192         */
193        public ReaderOrigin(final Reader origin) {
194            super(origin);
195        }
196
197        @Override
198        public Reader getReader(final Charset charset) throws IOException {
199            // No conversion
200            return get();
201        }
202    }
203
204    /**
205     * A {@link URI} origin.
206     */
207    public static class URIOrigin extends AbstractOrigin<URI, URIOrigin> {
208
209        /**
210         * Constructs a new instance for the given origin.
211         *
212         * @param origin The origin.
213         */
214        public URIOrigin(final URI origin) {
215            super(origin);
216        }
217
218        @Override
219        public File getFile() {
220            return getPath().toFile();
221        }
222
223        @Override
224        public Path getPath() {
225            return Paths.get(get());
226        }
227
228    }
229
230    /**
231     * An {@link Writer} origin.
232     * <p>
233     * This origin cannot provide other aspects.
234     * </p>
235     */
236    public static class WriterOrigin extends AbstractOrigin<Writer, WriterOrigin> {
237
238        /**
239         * Constructs a new instance for the given origin.
240         *
241         * @param origin The origin.
242         */
243        public WriterOrigin(final Writer origin) {
244            super(origin);
245        }
246
247        @Override
248        public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
249            // No conversion
250            return get();
251        }
252    }
253
254    /**
255     * The non-null origin.
256     */
257    final T origin;
258
259    /**
260     * Constructs a new instance for a subclass.
261     *
262     * @param origin The origin.
263     */
264    protected AbstractOrigin(final T origin) {
265        this.origin = Objects.requireNonNull(origin, "origin");
266    }
267
268    /**
269     * Gets the origin.
270     *
271     * @return the origin.
272     */
273    @Override
274    public T get() {
275        return origin;
276    }
277
278    /**
279     * Gets this origin as a byte array, if possible.
280     *
281     * @return this origin as a byte array, if possible.
282     * @throws IOException if an I/O error occurs.
283     * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
284     */
285    public byte[] getByteArray() throws IOException {
286        return Files.readAllBytes(getPath());
287    }
288
289    /**
290     * Gets this origin as a Path, if possible.
291     *
292     * @return this origin as a Path, if possible.
293     * @throws UnsupportedOperationException if this method is not implemented in a concrete subclass.
294     */
295    public File getFile() {
296        throw new UnsupportedOperationException(origin.toString());
297    }
298
299    /**
300     * Gets this origin as an InputStream, if possible.
301     *
302     * @param options options specifying how the file is opened
303     * @return this origin as an InputStream, if possible.
304     * @throws IOException if an I/O error occurs.
305     * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
306     */
307    public InputStream getInputStream(final OpenOption... options) throws IOException {
308        return Files.newInputStream(getPath(), options);
309    }
310
311    /**
312     * Gets this origin as an OutputStream, if possible.
313     *
314     * @param options options specifying how the file is opened
315     * @return this origin as an OutputStream, if possible.
316     * @throws IOException if an I/O error occurs.
317     * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
318     */
319    public OutputStream getOutputStream(final OpenOption... options) throws IOException {
320        return Files.newOutputStream(getPath(), options);
321    }
322
323    /**
324     * Gets this origin as a Path, if possible.
325     *
326     * @return this origin as a Path, if possible.
327     * @throws UnsupportedOperationException if this method is not implemented in a concrete subclass.
328     */
329    public Path getPath() {
330        throw new UnsupportedOperationException(origin.toString());
331    }
332
333    /**
334     * Gets a new Reader on the origin, buffered by default.
335     *
336     * @param charset the charset to use for decoding
337     * @return a new Reader on the origin.
338     * @throws IOException if an I/O error occurs opening the file.
339     */
340    public Reader getReader(final Charset charset) throws IOException {
341        return Files.newBufferedReader(getPath(), charset);
342    }
343
344    /**
345     * Gets a new Writer on the origin, buffered by default.
346     *
347     * @param charset the charset to use for encoding
348     * @param options options specifying how the file is opened
349     * @return a new Writer on the origin.
350     * @throws IOException if an I/O error occurs opening or creating the file.
351     * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
352     */
353    public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
354        return Files.newBufferedWriter(getPath(), charset, options);
355    }
356
357    @Override
358    public String toString() {
359        return origin.toString();
360    }
361}