View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.io.build;
19  
20  import java.io.ByteArrayInputStream;
21  import java.io.Closeable;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileNotFoundException;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.InputStreamReader;
28  import java.io.OutputStream;
29  import java.io.OutputStreamWriter;
30  import java.io.RandomAccessFile;
31  import java.io.Reader;
32  import java.io.Writer;
33  import java.net.URI;
34  import java.net.URLConnection;
35  import java.nio.channels.Channel;
36  import java.nio.channels.Channels;
37  import java.nio.channels.FileChannel;
38  import java.nio.channels.ReadableByteChannel;
39  import java.nio.channels.SeekableByteChannel;
40  import java.nio.channels.WritableByteChannel;
41  import java.nio.charset.Charset;
42  import java.nio.file.Files;
43  import java.nio.file.OpenOption;
44  import java.nio.file.Path;
45  import java.nio.file.Paths;
46  import java.nio.file.StandardOpenOption;
47  import java.time.Duration;
48  import java.util.Arrays;
49  import java.util.Objects;
50  import java.util.stream.Stream;
51  
52  import org.apache.commons.io.Charsets;
53  import org.apache.commons.io.IORandomAccessFile;
54  import org.apache.commons.io.IOUtils;
55  import org.apache.commons.io.RandomAccessFileMode;
56  import org.apache.commons.io.RandomAccessFiles;
57  import org.apache.commons.io.channels.ByteArraySeekableByteChannel;
58  import org.apache.commons.io.input.BufferedFileChannelInputStream;
59  import org.apache.commons.io.input.CharSequenceInputStream;
60  import org.apache.commons.io.input.CharSequenceReader;
61  import org.apache.commons.io.input.ReaderInputStream;
62  import org.apache.commons.io.output.RandomAccessFileOutputStream;
63  import org.apache.commons.io.output.WriterOutputStream;
64  
65  /**
66   * Abstracts and wraps an <em>origin</em> for builders, where an origin is a {@code byte[]}, {@link Channel}, {@link CharSequence}, {@link File},
67   * {@link InputStream}, {@link IORandomAccessFile}, {@link OutputStream}, {@link Path}, {@link RandomAccessFile}, {@link Reader}, {@link URI},
68   * or {@link Writer}.
69   * <p>
70   * An origin represents where bytes/characters come from or go to. Concrete subclasses
71   * expose only the operations that make sense for the underlying source or sink; invoking an unsupported operation
72   * results in {@link UnsupportedOperationException} (see, for example, {@link #getFile()} and {@link #getPath()}).
73   * </p>
74   * <p>
75   * An instance doesn't own its origin, it holds on to it to allow conversions. There are two use cases related to resource management for a Builder:
76   * </p>
77   * <ul>
78   * <li>
79   * A client allocates a {@linkplain Closeable} (or {@linkplain AutoCloseable}) resource, creates a Builder, and gives the Builder that resource by calling a
80   * setter method. No matter what happens next, the client is responsible for releasing the resource ({@code Closeable.close()}). In this case, the origin
81   * wraps but doesn't own the closeable resource. There is no transfer of ownership.
82   * </li>
83   * <li>
84   * A client creates a Builder and gives it a non-Closeable object, like a File or a Path. The client then calls the Builder's factory method
85   * (like {@linkplain #get()}), and that call returns a Closeable or a resource that requires releasing in some other way. No matter what happens next, the
86   * client is responsible for releasing that resource. In this case, the origin doesn't wrap a closeable resource.
87   * </li>
88   * </ul>
89   * <p>
90   * In both cases, the client causes the allocation and is responsible for releasing the resource.
91   * </p>
92   * <p>
93   * The table below summarizes which views and conversions are supported for each origin type.
94   * Column headers show the target view; cells indicate whether that view is available from the origin in that row.
95   * </p>
96   *
97   * <table>
98   *   <caption>Supported Conversions</caption>
99   *   <thead>
100  *     <tr>
101  *       <th>Origin Type</th>
102  *       <th>byte[]</th>
103  *       <th>CS</th>
104  *       <th>File</th>
105  *       <th>Path</th>
106  *       <th>RAF</th>
107  *       <th>IS</th>
108  *       <th>Reader</th>
109  *       <th>RBC</th>
110  *       <th>OS</th>
111  *       <th>Writer</th>
112  *       <th>WBC</th>
113  *       <th>Channel type<sup>2</sup></th>
114  *     </tr>
115  *   </thead>
116  *   <tbody>
117  *     <tr>
118  *       <td>byte[]</td>
119  *       <td>✔</td>
120  *       <td>✔</td>
121  *       <td>✖</td>
122  *       <td>✖</td>
123  *       <td>✖</td>
124  *       <td>✔</td>
125  *       <td>✔</td>
126  *       <td>✔</td>
127  *       <td>✖</td>
128  *       <td>✖</td>
129  *       <td>✖</td>
130  *       <td>SBC</td>
131  *     </tr>
132  *     <tr>
133  *       <td>{@link CharSequence} (CS)</td>
134  *       <td>✔</td>
135  *       <td>✔</td>
136  *       <td>✖</td>
137  *       <td>✖</td>
138  *       <td>✖</td>
139  *       <td>✔<sup>1</sup></td>
140  *       <td>✔</td>
141  *       <td>✔<sup>1</sup></td>
142  *       <td>✖</td>
143  *       <td>✖</td>
144  *       <td>✖</td>
145  *       <td>SBC</td>
146  *     </tr>
147  *     <tr>
148  *       <td>{@link File}</td>
149  *       <td>✔</td>
150  *       <td>✔</td>
151  *       <td>✔</td>
152  *       <td>✔</td>
153  *       <td>✔</td>
154  *       <td>✔</td>
155  *       <td>✔</td>
156  *       <td>✔</td>
157  *       <td>✔</td>
158  *       <td>✔</td>
159  *       <td>✔</td>
160  *       <td>FC</td>
161  *     </tr>
162  *     <tr>
163  *       <td>{@link Path}</td>
164  *       <td>✔</td>
165  *       <td>✔</td>
166  *       <td>✔</td>
167  *       <td>✔</td>
168  *       <td>✔</td>
169  *       <td>✔</td>
170  *       <td>✔</td>
171  *       <td>✔</td>
172  *       <td>✔</td>
173  *       <td>✔</td>
174  *       <td>✔</td>
175  *       <td>FC</td>
176  *     </tr>
177  *     <tr>
178  *       <td>{@link IORandomAccessFile}</td>
179  *       <td>✔</td>
180  *       <td>✔</td>
181  *       <td>✔</td>
182  *       <td>✔</td>
183  *       <td>✔</td>
184  *       <td>✔</td>
185  *       <td>✔</td>
186  *       <td>✔</td>
187  *       <td>✔</td>
188  *       <td>✔</td>
189  *       <td>✔</td>
190  *       <td>FC</td>
191  *     </tr>
192  *     <tr>
193  *       <td>{@link RandomAccessFile} (RAF)</td>
194  *       <td>✔</td>
195  *       <td>✔</td>
196  *       <td>✖</td>
197  *       <td>✖</td>
198  *       <td>✔</td>
199  *       <td>✔</td>
200  *       <td>✔</td>
201  *       <td>✔</td>
202  *       <td>✔</td>
203  *       <td>✔</td>
204  *       <td>✔</td>
205  *       <td>FC</td>
206  *     </tr>
207  *     <tr>
208  *       <td>{@link InputStream} (IS)</td>
209  *       <td>✔</td>
210  *       <td>✔</td>
211  *       <td>✖</td>
212  *       <td>✖</td>
213  *       <td>✖</td>
214  *       <td>✔</td>
215  *       <td>✔</td>
216  *       <td>✔</td>
217  *       <td>✖</td>
218  *       <td>✖</td>
219  *       <td>✖</td>
220  *       <td>RBC</td>
221  *     </tr>
222  *     <tr>
223  *       <td>{@link Reader}</td>
224  *       <td>✔</td>
225  *       <td>✔</td>
226  *       <td>✖</td>
227  *       <td>✖</td>
228  *       <td>✖</td>
229  *       <td>✔<sup>1</sup></td>
230  *       <td>✔</td>
231  *       <td>✔<sup>1</sup></td>
232  *       <td>✖</td>
233  *       <td>✖</td>
234  *       <td>✖</td>
235  *       <td>RBC</td>
236  *     </tr>
237  *     <tr>
238  *       <td>{@link ReadableByteChannel} (RBC)</td>
239  *       <td>✔</td>
240  *       <td>✔</td>
241  *       <td>✖</td>
242  *       <td>✖</td>
243  *       <td>✖</td>
244  *       <td>✔</td>
245  *       <td>✔</td>
246  *       <td>✔</td>
247  *       <td>✖</td>
248  *       <td>✖</td>
249  *       <td>✖</td>
250  *       <td>RBC</td>
251  *     </tr>
252  *     <tr>
253  *       <td>{@link OutputStream} (OS)</td>
254  *       <td>✖</td>
255  *       <td>✖</td>
256  *       <td>✖</td>
257  *       <td>✖</td>
258  *       <td>✖</td>
259  *       <td>✖</td>
260  *       <td>✖</td>
261  *       <td>✖</td>
262  *       <td>✔</td>
263  *       <td>✔</td>
264  *       <td>✔</td>
265  *       <td>WBC</td>
266  *     </tr>
267  *     <tr>
268  *       <td>{@link Writer}</td>
269  *       <td>✖</td>
270  *       <td>✖</td>
271  *       <td>✖</td>
272  *       <td>✖</td>
273  *       <td>✖</td>
274  *       <td>✖</td>
275  *       <td>✖</td>
276  *       <td>✖</td>
277  *       <td>✔<sup>1</sup></td>
278  *       <td>✔</td>
279  *       <td>✔<sup>1</sup></td>
280  *       <td>WBC</td>
281  *     </tr>
282  *     <tr>
283  *       <td>{@link WritableByteChannel} (WBC)</td>
284  *       <td>✖</td>
285  *       <td>✖</td>
286  *       <td>✖</td>
287  *       <td>✖</td>
288  *       <td>✖</td>
289  *       <td>✖</td>
290  *       <td>✖</td>
291  *       <td>✖</td>
292  *       <td>✔</td>
293  *       <td>✔</td>
294  *       <td>✔</td>
295  *       <td>WBC</td>
296  *     </tr>
297  *     <tr>
298  *       <td>{@link URI} (FileSystem)</td>
299  *       <td>✔</td>
300  *       <td>✔</td>
301  *       <td>✔</td>
302  *       <td>✔</td>
303  *       <td>✔</td>
304  *       <td>✔</td>
305  *       <td>✔</td>
306  *       <td>✔</td>
307  *       <td>✔</td>
308  *       <td>✔</td>
309  *       <td>✔</td>
310  *       <td>FC</td>
311  *     </tr>
312  *     <tr>
313  *       <td>{@link URI} (http/https)</td>
314  *       <td>✔</td>
315  *       <td>✔</td>
316  *       <td>✖</td>
317  *       <td>✖</td>
318  *       <td>✖</td>
319  *       <td>✔</td>
320  *       <td>✔</td>
321  *       <td>✔</td>
322  *       <td>✖</td>
323  *       <td>✖</td>
324  *       <td>✖</td>
325  *       <td>RBC</td>
326  *     </tr>
327  *   </tbody>
328  * </table>
329  *
330  * <p><strong>Legend</strong></p>
331  * <ul>
332  *   <li>✔ = Supported</li>
333  *   <li>✖ = Not supported (throws {@link UnsupportedOperationException})</li>
334  *   <li><sup>1</sup> = Characters are converted to bytes using the default {@link Charset}.</li>
335  *   <li><sup>2</sup> Minimum channel type provided by the origin:
336  *     <ul>
337  *         <li>RBC = {@linkplain ReadableByteChannel}</li>
338  *         <li>WBC = {@linkplain WritableByteChannel}</li>
339  *         <li>SBC = {@linkplain SeekableByteChannel}</li>
340  *         <li>FC = {@linkplain FileChannel}</li>
341  *     </ul>
342  *     The exact channel type may be a subtype of the minimum shown.
343  *   </li>
344  * </ul>
345  *
346  * @param <T> the type produced by the builder.
347  * @param <B> the concrete builder subclass type.
348  * @since 2.12.0
349  */
350 public abstract class AbstractOrigin<T, B extends AbstractOrigin<T, B>> extends AbstractSupplier<T, B> {
351 
352     /**
353      * A {@link RandomAccessFile} origin.
354      * <p>
355      * This origin cannot support File and Path since you cannot query a RandomAccessFile for those attributes; Use {@link IORandomAccessFileOrigin}
356      * instead.
357      * </p>
358      *
359      * @param <T> the type of instances to build.
360      * @param <B> the type of builder subclass.
361      */
362     public abstract static class AbstractRandomAccessFileOrigin<T extends RandomAccessFile, B extends AbstractRandomAccessFileOrigin<T, B>>
363             extends AbstractOrigin<T, B> {
364 
365         /**
366          * A {@link RandomAccessFile} origin.
367          * <p>
368          * Starting from this origin, you can everything except a Path and a File.
369          * </p>
370          *
371          * @param origin The origin, not null.
372          * @throws NullPointerException if {@code origin} is {@code null}.
373          */
374         public AbstractRandomAccessFileOrigin(final T origin) {
375             super(origin);
376         }
377 
378         @Override
379         public byte[] getByteArray() throws IOException {
380             final long longLen = origin.length();
381             if (longLen > Integer.MAX_VALUE) {
382                 throw new IllegalStateException("Origin too large.");
383             }
384             return RandomAccessFiles.read(origin, 0, (int) longLen);
385         }
386 
387         @Override
388         public byte[] getByteArray(final long position, final int length) throws IOException {
389             return RandomAccessFiles.read(origin, position, length);
390         }
391 
392         @Override
393         protected Channel getChannel(final OpenOption... options) throws IOException {
394             return getRandomAccessFile(options).getChannel();
395         }
396 
397         @Override
398         public CharSequence getCharSequence(final Charset charset) throws IOException {
399             return new String(getByteArray(), charset);
400         }
401 
402         @SuppressWarnings("resource")
403         @Override
404         public InputStream getInputStream(final OpenOption... options) throws IOException {
405             return BufferedFileChannelInputStream.builder().setFileChannel(origin.getChannel()).get();
406         }
407 
408         @Override
409         public OutputStream getOutputStream(final OpenOption... options) throws IOException {
410             return RandomAccessFileOutputStream.builder().setRandomAccessFile(origin).get();
411         }
412 
413         @Override
414         public T getRandomAccessFile(final OpenOption... openOption) {
415             // No conversion
416             return get();
417         }
418 
419         @Override
420         public Reader getReader(final Charset charset) throws IOException {
421             return new InputStreamReader(getInputStream(), Charsets.toCharset(charset));
422         }
423 
424         @Override
425         public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
426             return new OutputStreamWriter(getOutputStream(options), Charsets.toCharset(charset));
427         }
428 
429         @Override
430         public long size() throws IOException {
431             return origin.length();
432         }
433     }
434 
435     /**
436      * A {@code byte[]} origin.
437      */
438     public static class ByteArrayOrigin extends AbstractOrigin<byte[], ByteArrayOrigin> {
439 
440         /**
441          * Constructs a new instance for the given origin.
442          *
443          * @param origin The origin, not null.
444          * @throws NullPointerException if {@code origin} is {@code null}.
445          */
446         public ByteArrayOrigin(final byte[] origin) {
447             super(origin);
448         }
449 
450         /**
451          * {@inheritDoc}
452          *
453          * <p>
454          * No conversion should occur when calling this method.
455          * </p>
456          */
457         @Override
458         public byte[] getByteArray() {
459             // No conversion
460             return get();
461         }
462 
463         @Override
464         protected Channel getChannel(final OpenOption... options) throws IOException {
465             for (final OpenOption option : options) {
466                 if (option == StandardOpenOption.WRITE) {
467                     throw new UnsupportedOperationException("Only READ is supported for byte[] origins: " + Arrays.toString(options));
468                 }
469             }
470             return ByteArraySeekableByteChannel.wrap(getByteArray());
471         }
472 
473         /**
474          * {@inheritDoc}
475          * <p>
476          * The {@code options} parameter is ignored since a {@code byte[]} does not need an {@link OpenOption} to be read.
477          * </p>
478          */
479         @Override
480         public InputStream getInputStream(final OpenOption... options) throws IOException {
481             return new ByteArrayInputStream(origin);
482         }
483 
484         @Override
485         public Reader getReader(final Charset charset) throws IOException {
486             return new InputStreamReader(getInputStream(), Charsets.toCharset(charset));
487         }
488 
489         @Override
490         public long size() throws IOException {
491             return origin.length;
492         }
493 
494     }
495 
496     /**
497      * A {@link Channel} origin.
498      *
499      * @since 2.21.0
500      */
501     public static class ChannelOrigin extends AbstractOrigin<Channel, ChannelOrigin> {
502 
503         /**
504          * Constructs a new instance for the given origin.
505          *
506          * @param origin The origin, not null.
507          * @throws NullPointerException if {@code origin} is {@code null}.
508          */
509         public ChannelOrigin(final Channel origin) {
510             super(origin);
511         }
512 
513         @Override
514         public byte[] getByteArray() throws IOException {
515             return IOUtils.toByteArray(getInputStream());
516         }
517 
518         /**
519          * {@inheritDoc}
520          *
521          * <p>
522          * No conversion should occur when calling this method.
523          * </p>
524          */
525         @Override
526         protected Channel getChannel(final OpenOption... options) throws IOException {
527             // No conversion
528             return get();
529         }
530 
531         @Override
532         public InputStream getInputStream(final OpenOption... options) throws IOException {
533             return Channels.newInputStream(getChannel(ReadableByteChannel.class, options));
534         }
535 
536         @Override
537         public OutputStream getOutputStream(final OpenOption... options) throws IOException {
538             return Channels.newOutputStream(getChannel(WritableByteChannel.class, options));
539         }
540 
541         @Override
542         public Reader getReader(final Charset charset) throws IOException {
543             return Channels.newReader(
544                     getChannel(ReadableByteChannel.class),
545                     Charsets.toCharset(charset).newDecoder(),
546                     -1);
547         }
548 
549         @Override
550         public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
551             return Channels.newWriter(getChannel(WritableByteChannel.class, options), Charsets.toCharset(charset).newEncoder(), -1);
552         }
553 
554         @Override
555         public long size() throws IOException {
556             if (origin instanceof SeekableByteChannel) {
557                 return ((SeekableByteChannel) origin).size();
558             }
559             throw unsupportedOperation("size");
560         }
561     }
562 
563     /**
564      * A {@link CharSequence} origin.
565      */
566     public static class CharSequenceOrigin extends AbstractOrigin<CharSequence, CharSequenceOrigin> {
567 
568         /**
569          * Constructs a new instance for the given origin.
570          *
571          * @param origin The origin, not null.
572          * @throws NullPointerException if {@code origin} is {@code null}.
573          */
574         public CharSequenceOrigin(final CharSequence origin) {
575             super(origin);
576         }
577 
578         @Override
579         public byte[] getByteArray() {
580             // TODO Pass in a Charset? Consider if call sites actually need this.
581             return origin.toString().getBytes(Charset.defaultCharset());
582         }
583 
584         @Override
585         protected Channel getChannel(final OpenOption... options) throws IOException {
586             for (final OpenOption option : options) {
587                 if (option == StandardOpenOption.WRITE) {
588                     throw new UnsupportedOperationException("Only READ is supported for CharSequence origins: " + Arrays.toString(options));
589                 }
590             }
591             return ByteArraySeekableByteChannel.wrap(getByteArray());
592         }
593 
594         /**
595          * {@inheritDoc}
596          * <p>
597          * The {@code charset} parameter is ignored since a {@link CharSequence} does not need a {@link Charset} to be read.
598          * </p>
599          * <p>
600          * No conversion should occur when calling this method.
601          * </p>
602          */
603         @Override
604         public CharSequence getCharSequence(final Charset charset) {
605             // No conversion
606             return get();
607         }
608 
609         /**
610          * {@inheritDoc}
611          * <p>
612          * The {@code options} parameter is ignored since a {@link CharSequence} does not need an {@link OpenOption} to be read.
613          * </p>
614          */
615         @Override
616         public InputStream getInputStream(final OpenOption... options) throws IOException {
617             // TODO Pass in a Charset? Consider if call sites actually need this.
618             return CharSequenceInputStream.builder().setCharSequence(getCharSequence(Charset.defaultCharset())).get();
619         }
620 
621         /**
622          * {@inheritDoc}
623          * <p>
624          * The {@code charset} parameter is ignored since a {@link CharSequence} does not need a {@link Charset} to be read.
625          * </p>
626          */
627         @Override
628         public Reader getReader(final Charset charset) throws IOException {
629             return new CharSequenceReader(get());
630         }
631 
632         @Override
633         public long size() throws IOException {
634             return origin.length();
635         }
636 
637     }
638 
639     /**
640      * A {@link File} origin.
641      * <p>
642      * 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.
643      * </p>
644      */
645     public static class FileOrigin extends AbstractOrigin<File, FileOrigin> {
646 
647         /**
648          * Constructs a new instance for the given origin.
649          *
650          * @param origin The origin, not null.
651          * @throws NullPointerException if {@code origin} is {@code null}.
652          */
653         public FileOrigin(final File origin) {
654             super(origin);
655         }
656 
657         @Override
658         public byte[] getByteArray(final long position, final int length) throws IOException {
659             try (RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(origin)) {
660                 return RandomAccessFiles.read(raf, position, length);
661             }
662         }
663 
664         @Override
665         protected Channel getChannel(final OpenOption... options) throws IOException {
666             return Files.newByteChannel(getPath(), options);
667         }
668 
669         /**
670          * {@inheritDoc}
671          *
672          * <p>
673          * No conversion should occur when calling this method.
674          * </p>
675          */
676         @Override
677         public File getFile() {
678             // No conversion
679             return get();
680         }
681 
682         @Override
683         public Path getPath() {
684             return get().toPath();
685         }
686     }
687 
688     /**
689      * An {@link InputStream} origin.
690      * <p>
691      * This origin cannot provide some of the other aspects.
692      * </p>
693      */
694     public static class InputStreamOrigin extends AbstractOrigin<InputStream, InputStreamOrigin> {
695 
696         /**
697          * Constructs a new instance for the given origin.
698          *
699          * @param origin The origin, not null.
700          * @throws NullPointerException if {@code origin} is {@code null}.
701          */
702         public InputStreamOrigin(final InputStream origin) {
703             super(origin);
704         }
705 
706         @Override
707         public byte[] getByteArray() throws IOException {
708             return IOUtils.toByteArray(origin);
709         }
710 
711         @Override
712         protected Channel getChannel(final OpenOption... options) throws IOException {
713             return Channels.newChannel(getInputStream(options));
714         }
715 
716         /**
717          * {@inheritDoc}
718          * <p>
719          * The {@code options} parameter is ignored since a {@link InputStream} does not need an {@link OpenOption} to be read.
720          * </p>
721          * <p>
722          * No conversion should occur when calling this method.
723          * </p>
724          */
725         @Override
726         public InputStream getInputStream(final OpenOption... options) {
727             // No conversion
728             return get();
729         }
730 
731         @Override
732         public Reader getReader(final Charset charset) throws IOException {
733             return new InputStreamReader(getInputStream(), Charsets.toCharset(charset));
734         }
735 
736         @Override
737         public long size() throws IOException {
738             if (origin instanceof FileInputStream) {
739                 return ((FileInputStream) origin).getChannel().size();
740             }
741             throw unsupportedOperation("size");
742         }
743     }
744 
745     /**
746      * An {@link IORandomAccessFile} origin.
747      *
748      * @since 2.18.0
749      */
750     public static class IORandomAccessFileOrigin extends AbstractRandomAccessFileOrigin<IORandomAccessFile, IORandomAccessFileOrigin> {
751 
752         /**
753          * A {@link RandomAccessFile} origin.
754          *
755          * @param origin The origin, not null.
756          */
757         public IORandomAccessFileOrigin(final IORandomAccessFile origin) {
758             super(origin);
759         }
760 
761         @SuppressWarnings("resource")
762         @Override
763         public File getFile() {
764             return get().getFile();
765         }
766 
767         @Override
768         public Path getPath() {
769             return getFile().toPath();
770         }
771 
772     }
773 
774     /**
775      * An {@link OutputStream} origin.
776      * <p>
777      * This origin cannot provide some of the other aspects.
778      * </p>
779      */
780     public static class OutputStreamOrigin extends AbstractOrigin<OutputStream, OutputStreamOrigin> {
781 
782         /**
783          * Constructs a new instance for the given origin.
784          *
785          * @param origin The origin, not null.
786          * @throws NullPointerException if {@code origin} is {@code null}.
787          */
788         public OutputStreamOrigin(final OutputStream origin) {
789             super(origin);
790         }
791 
792         @Override
793         protected Channel getChannel(final OpenOption... options) throws IOException {
794             return Channels.newChannel(getOutputStream(options));
795         }
796 
797         /**
798          * {@inheritDoc}
799          * <p>
800          * The {@code options} parameter is ignored since a {@link OutputStream} does not need an {@link OpenOption} to be written.
801          * </p>
802          * <p>
803          * No conversion should occur when calling this method.
804          * </p>
805          */
806         @Override
807         public OutputStream getOutputStream(final OpenOption... options) {
808             // No conversion
809             return get();
810         }
811 
812         /**
813          * {@inheritDoc}
814          * <p>
815          * The {@code options} parameter is ignored since a {@link OutputStream} does not need an {@link OpenOption} to be written.
816          * </p>
817          */
818         @Override
819         public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
820             return new OutputStreamWriter(origin, Charsets.toCharset(charset));
821         }
822     }
823 
824     /**
825      * A {@link Path} origin.
826      * <p>
827      * 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.
828      * </p>
829      */
830     public static class PathOrigin extends AbstractOrigin<Path, PathOrigin> {
831 
832         /**
833          * Constructs a new instance for the given origin.
834          *
835          * @param origin The origin, not null.
836          * @throws NullPointerException if {@code origin} is {@code null}.
837          */
838         public PathOrigin(final Path origin) {
839             super(origin);
840         }
841 
842         @Override
843         public byte[] getByteArray(final long position, final int length) throws IOException {
844             return RandomAccessFileMode.READ_ONLY.apply(origin, raf -> RandomAccessFiles.read(raf, position, length));
845         }
846 
847         @Override
848         protected Channel getChannel(final OpenOption... options) throws IOException {
849             return Files.newByteChannel(getPath(), options);
850         }
851 
852         @Override
853         public File getFile() {
854             return get().toFile();
855         }
856 
857         /**
858          * {@inheritDoc}
859          *
860          * <p>
861          * No conversion should occur when calling this method.
862          * </p>
863          */
864         @Override
865         public Path getPath() {
866             // No conversion
867             return get();
868         }
869     }
870 
871     /**
872      * A {@link RandomAccessFile} origin.
873      * <p>
874      * This origin cannot support File and Path since you cannot query a RandomAccessFile for those attributes; Use {@link IORandomAccessFileOrigin}
875      * instead.
876      * </p>
877      */
878     public static class RandomAccessFileOrigin extends AbstractRandomAccessFileOrigin<RandomAccessFile, RandomAccessFileOrigin> {
879 
880         /**
881          * A {@link RandomAccessFile} origin.
882          * <p>
883          * Starting from this origin, you can everything except a Path and a File.
884          * </p>
885          *
886          * @param origin The origin, not null.
887          */
888         public RandomAccessFileOrigin(final RandomAccessFile origin) {
889             super(origin);
890         }
891 
892     }
893 
894     /**
895      * A {@link Reader} origin.
896      * <p>
897      * This origin cannot provide conversions to other aspects.
898      * </p>
899      */
900     public static class ReaderOrigin extends AbstractOrigin<Reader, ReaderOrigin> {
901 
902         /**
903          * Constructs a new instance for the given origin.
904          *
905          * @param origin The origin, not null.
906          * @throws NullPointerException if {@code origin} is {@code null}.
907          */
908         public ReaderOrigin(final Reader origin) {
909             super(origin);
910         }
911 
912         @Override
913         public byte[] getByteArray() throws IOException {
914             // TODO Pass in a Charset? Consider if call sites actually need this.
915             return IOUtils.toByteArray(origin, Charset.defaultCharset());
916         }
917 
918         @Override
919         protected Channel getChannel(final OpenOption... options) throws IOException {
920             return Channels.newChannel(getInputStream());
921         }
922 
923         /**
924          * {@inheritDoc}
925          * <p>
926          * The {@code charset} parameter is ignored since a {@link Reader} does not need a {@link Charset} to be read.
927          * </p>
928          */
929         @Override
930         public CharSequence getCharSequence(final Charset charset) throws IOException {
931             return IOUtils.toString(origin);
932         }
933 
934         /**
935          * {@inheritDoc}
936          * <p>
937          * The {@code options} parameter is ignored since a {@link Reader} does not need an {@link OpenOption} to be read.
938          * </p>
939          */
940         @Override
941         public InputStream getInputStream(final OpenOption... options) throws IOException {
942             // TODO Pass in a Charset? Consider if call sites actually need this.
943             return ReaderInputStream.builder().setReader(origin).setCharset(Charset.defaultCharset()).get();
944         }
945 
946         /**
947          * {@inheritDoc}
948          * <p>
949          * The {@code charset} parameter is ignored since a {@link Reader} does not need a {@link Charset} to be read.
950          * </p>
951          * <p>
952          * No conversion should occur when calling this method.
953          * </p>
954          */
955         @Override
956         public Reader getReader(final Charset charset) throws IOException {
957             // No conversion
958             return get();
959         }
960     }
961 
962     /**
963      * A {@link URI} origin.
964      */
965     public static class URIOrigin extends AbstractOrigin<URI, URIOrigin> {
966 
967         /**
968          * Options for connect and read from a URI.
969          *
970          * @since 2.22.0
971          */
972         public static final class URIOpenOption implements OpenOption {
973 
974             /**
975              * Builds URIOpenOption.
976              */
977             public static class Builder extends AbstractSupplier<URIOpenOption, Builder> {
978 
979                 private Duration connectTimeout;
980                 private Duration readTimeout;
981 
982                 /**
983                  * Constructs a new instance.
984                  */
985                 public Builder() {
986                     // empty
987                 }
988 
989                 @Override
990                 public URIOpenOption get() {
991                     return new URIOpenOption(this);
992                 }
993 
994                 /**
995                  * Sets the connect timeout duration.
996                  *
997                  * @param connectTimeout the connect timeout duration.
998                  * @return {@code this instance}.
999                  */
1000                 public Builder setConnectTimeout(final Duration connectTimeout) {
1001                     this.connectTimeout = connectTimeout;
1002                     return asThis();
1003                 }
1004 
1005                 /**
1006                  * Sets the read timeout duration.
1007                  *
1008                  * @param readTimeout the read timeout duration.
1009                  * @return {@code this instance}.
1010                  */
1011                 public Builder setReadTimeout(final Duration readTimeout) {
1012                     this.readTimeout = readTimeout;
1013                     return asThis();
1014                 }
1015             }
1016 
1017             /**
1018              * Creates a new builder.
1019              *
1020              * @return a new builder.
1021              */
1022             public static Builder builder() {
1023                 return new Builder();
1024             }
1025 
1026             private final Duration connectTimeout;
1027 
1028             private final Duration readTimeout;
1029 
1030             private URIOpenOption(final Builder builder) {
1031                 connectTimeout = builder.connectTimeout;
1032                 readTimeout = builder.readTimeout;
1033             }
1034 
1035             @Override
1036             public String toString() {
1037                 return "URIOpenOption [connectTimeout=" + connectTimeout + ", readTimeout=" + readTimeout + "]";
1038             }
1039         }
1040 
1041         private static final String SCHEME_HTTPS = "https";
1042         private static final String SCHEME_HTTP = "http";
1043 
1044         /**
1045          * Constructs a new instance for the given origin.
1046          *
1047          * @param origin The origin, not null.
1048          * @throws NullPointerException if {@code origin} is {@code null}.
1049          */
1050         public URIOrigin(final URI origin) {
1051             super(origin);
1052         }
1053 
1054         /**
1055          * {@inheritDoc}
1056          *
1057          * @see URIOpenOption
1058          */
1059         @Override
1060         protected Channel getChannel(final OpenOption... options) throws IOException {
1061             final URI uri = get();
1062             final String scheme = uri.getScheme();
1063             if (SCHEME_HTTP.equalsIgnoreCase(scheme) || SCHEME_HTTPS.equalsIgnoreCase(scheme)) {
1064                 return Channels.newChannel(getInputStream(uri, options));
1065             }
1066             return Files.newByteChannel(getPath(), options);
1067         }
1068 
1069         @Override
1070         public File getFile() {
1071             return getPath().toFile();
1072         }
1073 
1074         /**
1075          * {@inheritDoc}
1076          * <p>
1077          * Set timeouts with a {@link URIOpenOption}.
1078          * </p>
1079          *
1080          * @see URIOpenOption
1081          * @see URLConnection#setConnectTimeout(int)
1082          * @see URLConnection#setReadTimeout(int)
1083          */
1084         @Override
1085         public InputStream getInputStream(final OpenOption... options) throws IOException {
1086             final URI uri = get();
1087             final String scheme = uri.getScheme();
1088             if (SCHEME_HTTP.equalsIgnoreCase(scheme) || SCHEME_HTTPS.equalsIgnoreCase(scheme)) {
1089                 return getInputStream(uri, options);
1090             }
1091             return Files.newInputStream(getPath(), options);
1092         }
1093 
1094         private InputStream getInputStream(final URI uri, final OpenOption... options) throws IOException {
1095             final URLConnection connection = uri.toURL().openConnection();
1096             if (options != null) {
1097                 Stream.of(options).forEach(option -> {
1098                     if (option instanceof URIOpenOption) {
1099                         final URIOpenOption connOption = (URIOpenOption) option;
1100                         if (connOption.connectTimeout != null) {
1101                             connection.setConnectTimeout(toMillis(connOption.connectTimeout));
1102                         }
1103                         if (connOption.readTimeout != null) {
1104                             connection.setReadTimeout(toMillis(connOption.readTimeout));
1105                         }
1106                     }
1107                 });
1108             }
1109             return connection.getInputStream();
1110         }
1111 
1112         @Override
1113         public Path getPath() {
1114             return Paths.get(get());
1115         }
1116 
1117         private int toMillis(final Duration duration) {
1118             return Math.toIntExact(duration.toMillis());
1119         }
1120     }
1121 
1122     /**
1123      * A {@link Writer} origin.
1124      * <p>
1125      * This origin cannot provide conversions to other aspects.
1126      * </p>
1127      */
1128     public static class WriterOrigin extends AbstractOrigin<Writer, WriterOrigin> {
1129 
1130         /**
1131          * Constructs a new instance for the given origin.
1132          *
1133          * @param origin The origin, not null.
1134          * @throws NullPointerException if {@code origin} is {@code null}.
1135          */
1136         public WriterOrigin(final Writer origin) {
1137             super(origin);
1138         }
1139 
1140         @Override
1141         protected Channel getChannel(final OpenOption... options) throws IOException {
1142             return Channels.newChannel(getOutputStream());
1143         }
1144 
1145         /**
1146          * {@inheritDoc}
1147          * <p>
1148          * The {@code options} parameter is ignored since a {@link Writer} does not need an {@link OpenOption} to be written.
1149          * </p>
1150          */
1151         @Override
1152         public OutputStream getOutputStream(final OpenOption... options) throws IOException {
1153             // TODO Pass in a Charset? Consider if call sites actually need this.
1154             return WriterOutputStream.builder().setWriter(origin).setCharset(Charset.defaultCharset()).get();
1155         }
1156 
1157         /**
1158          * {@inheritDoc}
1159          * <p>
1160          * The {@code charset} parameter is ignored since a {@link Writer} does not need a {@link Charset} to be written.
1161          * </p>
1162          * <p>
1163          * The {@code options} parameter is ignored since a {@link Writer} does not need an {@link OpenOption} to be written.
1164          * </p>
1165          * <p>
1166          * No conversion should occur when calling this method.
1167          * </p>
1168          */
1169         @Override
1170         public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
1171             // No conversion
1172             return get();
1173         }
1174     }
1175 
1176     /**
1177      * The non-null origin.
1178      */
1179     final T origin;
1180 
1181     /**
1182      * Constructs a new instance for subclasses.
1183      *
1184      * @param origin The origin, not null.
1185      * @throws NullPointerException if {@code origin} is {@code null}.
1186      */
1187     protected AbstractOrigin(final T origin) {
1188         this.origin = Objects.requireNonNull(origin, "origin");
1189     }
1190 
1191     /**
1192      * Gets the origin, never null.
1193      *
1194      * @return the origin, never null.
1195      */
1196     @Override
1197     public T get() {
1198         return origin;
1199     }
1200 
1201     /**
1202      * Gets this origin as a byte array, if possible.
1203      *
1204      * @return this origin as a byte array, if possible.
1205      * @throws IOException                   if an I/O error occurs.
1206      * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
1207      */
1208     public byte[] getByteArray() throws IOException {
1209         return Files.readAllBytes(getPath());
1210     }
1211 
1212     /**
1213      * Gets a portion of this origin as a byte array, if possible.
1214      *
1215      * @param position the initial index of the range to be copied, inclusive.
1216      * @param length   How many bytes to copy.
1217      * @return this origin as a byte array, if possible.
1218      * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
1219      * @throws ArithmeticException           if the {@code position} overflows an int.
1220      * @throws IOException                   if an I/O error occurs.
1221      * @since 2.13.0
1222      */
1223     public byte[] getByteArray(final long position, final int length) throws IOException {
1224         final byte[] bytes = getByteArray();
1225         // Checks for int overflow.
1226         final int start = Math.toIntExact(position);
1227         if (start < 0 || length < 0 || start + length < 0 || start + length > bytes.length) {
1228             throw new IllegalArgumentException("Couldn't read array (start: " + start + ", length: " + length + ", data length: " + bytes.length + ").");
1229         }
1230         return Arrays.copyOfRange(bytes, start, start + length);
1231     }
1232 
1233     /**
1234      * Gets this origin as a Channel of the given type, if possible.
1235      *
1236      * @param channelType The type of channel to return.
1237      * @param options Options specifying how a file-based origin is opened, ignored otherwise.
1238      * @return A new Channel on the origin of the given type.
1239      * @param <C> The type of channel to return.
1240      * @throws IOException                   If an I/O error occurs.
1241      * @throws UnsupportedOperationException If this origin cannot be converted to a channel of the given type.
1242      * @see #getChannel(OpenOption...)
1243      * @since 2.21.0
1244      */
1245     public final <C extends Channel> C getChannel(final Class<C> channelType, final OpenOption... options) throws IOException {
1246         Objects.requireNonNull(channelType, "channelType");
1247         final Channel channel = getChannel(options);
1248         if (channelType.isInstance(channel)) {
1249             return channelType.cast(channel);
1250         }
1251         throw unsupportedChannelType(channelType);
1252     }
1253 
1254     /**
1255      * Gets this origin as a Channel, if possible.
1256      *
1257      * @param options Options specifying how a file-based origin is opened, ignored otherwise.
1258      * @return A new Channel on the origin.
1259      * @throws IOException                   If an I/O error occurs.
1260      * @throws UnsupportedOperationException If this origin cannot be converted to a channel.
1261      * @see #getChannel(Class, OpenOption...)
1262      * @since 2.21.0
1263      */
1264     protected Channel getChannel(final OpenOption... options) throws IOException {
1265         throw unsupportedOperation("getChannel");
1266     }
1267 
1268     /**
1269      * Gets this origin as a byte array, if possible.
1270      *
1271      * @param charset The charset to use if conversion from bytes is needed.
1272      * @return this origin as a byte array, if possible.
1273      * @throws IOException                   if an I/O error occurs.
1274      * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
1275      */
1276     public CharSequence getCharSequence(final Charset charset) throws IOException {
1277         return new String(getByteArray(), charset);
1278     }
1279 
1280     /**
1281      * Gets this origin as a File, if possible.
1282      *
1283      * @return this origin as a File, if possible.
1284      * @throws UnsupportedOperationException if this method is not implemented in a concrete subclass.
1285      */
1286     public File getFile() {
1287         throw unsupportedOperation("getFile");
1288     }
1289 
1290     /**
1291      * Gets this origin as an InputStream, if possible.
1292      *
1293      * @param options options specifying how the file is opened.
1294      * @return this origin as an InputStream, if possible.
1295      * @throws IOException                   if an I/O error occurs.
1296      * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
1297      */
1298     public InputStream getInputStream(final OpenOption... options) throws IOException {
1299         return Files.newInputStream(getPath(), options);
1300     }
1301 
1302     /**
1303      * Gets this origin as an OutputStream, if possible.
1304      *
1305      * @param options options specifying how the file is opened.
1306      * @return this origin as an OutputStream, if possible.
1307      * @throws IOException                   if an I/O error occurs.
1308      * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
1309      */
1310     public OutputStream getOutputStream(final OpenOption... options) throws IOException {
1311         return Files.newOutputStream(getPath(), options);
1312     }
1313 
1314     /**
1315      * Gets this origin as a Path, if possible.
1316      *
1317      * @return this origin as a Path, if possible.
1318      * @throws UnsupportedOperationException if this method is not implemented in a concrete subclass.
1319      */
1320     public Path getPath() {
1321         throw unsupportedOperation("getPath");
1322     }
1323 
1324     /**
1325      * Gets this origin as a RandomAccessFile, if possible.
1326      *
1327      * @param openOption options like {@link StandardOpenOption}.
1328      * @return this origin as a RandomAccessFile, if possible.
1329      * @throws FileNotFoundException         See {@link RandomAccessFile#RandomAccessFile(File, String)}.
1330      * @throws UnsupportedOperationException if this method is not implemented in a concrete subclass.
1331      * @since 2.18.0
1332      */
1333     public RandomAccessFile getRandomAccessFile(final OpenOption... openOption) throws FileNotFoundException {
1334         return RandomAccessFileMode.valueOf(openOption).create(getFile());
1335     }
1336 
1337     /**
1338      * Gets a new Reader on the origin, buffered by default.
1339      *
1340      * @param charset the charset to use for decoding, null maps to the default Charset.
1341      * @return a new Reader on the origin.
1342      * @throws IOException if an I/O error occurs opening the file.
1343      */
1344     public Reader getReader(final Charset charset) throws IOException {
1345         return Files.newBufferedReader(getPath(), Charsets.toCharset(charset));
1346     }
1347 
1348     /**
1349      * Gets simple name of the underlying class.
1350      *
1351      * @return The simple name of the underlying class.
1352      */
1353     private String getSimpleClassName() {
1354         return getClass().getSimpleName();
1355     }
1356 
1357     /**
1358      * Gets a new Writer on the origin, buffered by default.
1359      *
1360      * @param charset the charset to use for encoding.
1361      * @param options options specifying how the file is opened.
1362      * @return a new Writer on the origin.
1363      * @throws IOException                   if an I/O error occurs opening or creating the file.
1364      * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
1365      */
1366     public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
1367         return Files.newBufferedWriter(getPath(), Charsets.toCharset(charset), options);
1368     }
1369 
1370     /**
1371      * Gets the size of the origin, if possible.
1372      *
1373      * @return the size of the origin in bytes or characters.
1374      * @throws IOException if an I/O error occurs.
1375      * @since 2.13.0
1376      */
1377     public long size() throws IOException {
1378         return Files.size(getPath());
1379     }
1380 
1381     @Override
1382     public String toString() {
1383         return getSimpleClassName() + "[" + origin.toString() + "]";
1384     }
1385 
1386     UnsupportedOperationException unsupportedChannelType(final Class<? extends Channel> channelType) {
1387         return new UnsupportedOperationException(String.format(
1388                 "%s#getChannel(%s) for %s origin %s",
1389                 getSimpleClassName(),
1390                 channelType.getSimpleName(),
1391                 origin.getClass().getSimpleName(),
1392                 origin));
1393     }
1394 
1395     UnsupportedOperationException unsupportedOperation(final String method) {
1396         return new UnsupportedOperationException(String.format(
1397                 "%s#%s() for %s origin %s",
1398                 getSimpleClassName(), method, origin.getClass().getSimpleName(), origin));
1399     }
1400 }