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