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