001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.io.build;
019
020import java.io.ByteArrayInputStream;
021import java.io.Closeable;
022import java.io.File;
023import java.io.FileInputStream;
024import java.io.FileNotFoundException;
025import java.io.IOException;
026import java.io.InputStream;
027import java.io.InputStreamReader;
028import java.io.OutputStream;
029import java.io.OutputStreamWriter;
030import java.io.RandomAccessFile;
031import java.io.Reader;
032import java.io.Writer;
033import java.net.URI;
034import java.nio.channels.Channel;
035import java.nio.channels.Channels;
036import java.nio.channels.FileChannel;
037import java.nio.channels.ReadableByteChannel;
038import java.nio.channels.SeekableByteChannel;
039import java.nio.channels.WritableByteChannel;
040import java.nio.charset.Charset;
041import java.nio.file.Files;
042import java.nio.file.OpenOption;
043import java.nio.file.Path;
044import java.nio.file.Paths;
045import java.nio.file.StandardOpenOption;
046import java.util.Arrays;
047import java.util.Objects;
048
049import org.apache.commons.io.Charsets;
050import org.apache.commons.io.IORandomAccessFile;
051import org.apache.commons.io.IOUtils;
052import org.apache.commons.io.RandomAccessFileMode;
053import org.apache.commons.io.RandomAccessFiles;
054import org.apache.commons.io.channels.ByteArraySeekableByteChannel;
055import org.apache.commons.io.input.BufferedFileChannelInputStream;
056import org.apache.commons.io.input.CharSequenceInputStream;
057import org.apache.commons.io.input.CharSequenceReader;
058import org.apache.commons.io.input.ReaderInputStream;
059import org.apache.commons.io.output.RandomAccessFileOutputStream;
060import org.apache.commons.io.output.WriterOutputStream;
061
062/**
063 * Abstracts and wraps an <em>origin</em> for builders, where an origin is a {@code byte[]}, {@link Channel}, {@link CharSequence}, {@link File},
064 * {@link InputStream}, {@link IORandomAccessFile}, {@link OutputStream}, {@link Path}, {@link RandomAccessFile}, {@link Reader}, {@link URI},
065 * or {@link Writer}.
066 * <p>
067 * An origin represents where bytes/characters come from or go to. Concrete subclasses
068 * expose only the operations that make sense for the underlying source or sink; invoking an unsupported operation
069 * results in {@link UnsupportedOperationException} (see, for example, {@link #getFile()} and {@link #getPath()}).
070 * </p>
071 * <p>
072 * 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:
073 * </p>
074 * <ul>
075 * <li>
076 * A client allocates a {@linkplain Closeable} (or {@linkplain AutoCloseable}) resource, creates a Builder, and gives the Builder that resource by calling a
077 * setter method. No matter what happens next, the client is responsible for releasing the resource ({@code Closeable.close()}). In this case, the origin
078 * wraps but doesn't own the closeable resource. There is no transfer of ownership.
079 * </li>
080 * <li>
081 * 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
082 * (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
083 * client is responsible for releasing that resource. In this case, the origin doesn't wrap a closeable resource.
084 * </li>
085 * </ul>
086 * <p>
087 * In both cases, the client causes the allocation and is responsible for releasing the resource.
088 * </p>
089 * <p>
090 * The table below summarizes which views and conversions are supported for each origin type.
091 * Column headers show the target view; cells indicate whether that view is available from the origin in that row.
092 * </p>
093 *
094 * <table>
095 *   <caption>Supported Conversions</caption>
096 *   <thead>
097 *     <tr>
098 *       <th>Origin Type</th>
099 *       <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 */
347public 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}