    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.io.build;
    019
    020import java.io.ByteArrayInputStream;
    021import java.io.File;
    022import java.io.FileNotFoundException;
    023import java.io.IOException;
    024import java.io.InputStream;
    025import java.io.InputStreamReader;
    026import java.io.OutputStream;
    027import java.io.OutputStreamWriter;
    028import java.io.RandomAccessFile;
    029import java.io.Reader;
    030import java.io.Writer;
    031import java.net.URI;
    032import java.nio.charset.Charset;
    033import java.nio.file.Files;
    034import java.nio.file.OpenOption;
    035import java.nio.file.Path;
    036import java.nio.file.Paths;
    037import java.nio.file.StandardOpenOption;
    038import java.nio.file.spi.FileSystemProvider;
    039import java.util.Arrays;
    040import java.util.Objects;
    041
    042import org.apache.commons.io.IORandomAccessFile;
    043import org.apache.commons.io.IOUtils;
    044import org.apache.commons.io.RandomAccessFileMode;
    045import org.apache.commons.io.RandomAccessFiles;
    046import org.apache.commons.io.file.spi.FileSystemProviders;
    047import org.apache.commons.io.input.BufferedFileChannelInputStream;
    048import org.apache.commons.io.input.CharSequenceInputStream;
    049import org.apache.commons.io.input.CharSequenceReader;
    050import org.apache.commons.io.input.ReaderInputStream;
    051import org.apache.commons.io.output.RandomAccessFileOutputStream;
    052import org.apache.commons.io.output.WriterOutputStream;
    053
    054/**
    055 * Abstracts the origin of data for builders like a {@link File}, {@link Path}, {@link Reader}, {@link Writer}, {@link InputStream}, {@link OutputStream}, and
    056 * {@link URI}.
    057 * <p>
    058 * Some methods may throw {@link UnsupportedOperationException} if that method is not implemented in a concrete subclass, see {@link #getFile()} and
    059 * {@link #getPath()}.
    060 * </p>
    061 *
    062 * @param <T> the type of instances to build.
    063 * @param <B> the type of builder subclass.
    064 * @since 2.12.0
    065 */
    066public abstract class AbstractOrigin<T, B extends AbstractOrigin<T, B>> extends AbstractSupplier<T, B> {
    067
    068    /**
    069     * A {@link RandomAccessFile} origin.
    070     * <p>
    071     * This origin cannot support File and Path since you cannot query a RandomAccessFile for those attributes; Use {@link IORandomAccessFileOrigin}
    072     * instead.
    073     * </p>
    074     *
    075     * @param <T> the type of instances to build.
    076     * @param <B> the type of builder subclass.
    077     */
    078    public static abstract class AbstractRandomAccessFileOrigin<T extends RandomAccessFile, B extends AbstractRandomAccessFileOrigin<T, B>>
    079            extends AbstractOrigin<T, B> {
    080
    081        /**
    082         * A {@link RandomAccessFile} origin.
    083         * <p>
    084         * Starting from this origin, you can everything except a Path and a File.
    085         * </p>
    086         *
    087         * @param origin The origin.
    088         */
    089        public AbstractRandomAccessFileOrigin(final T origin) {
    090            super(origin);
    091        }
    092
    093        @Override
    094        public byte[] getByteArray() throws IOException {
    095            final long longLen = origin.length();
    096            if (longLen > Integer.MAX_VALUE) {
    097                throw new IllegalStateException("Origin too large.");
    098            }
    099            return RandomAccessFiles.read(origin, 0, (int) longLen);
    100        }
    101
    102        @Override
    103        public byte[] getByteArray(final long position, final int length) throws IOException {
    104            return RandomAccessFiles.read(origin, position, length);
    105        }
    106
    107        @Override
    108        public CharSequence getCharSequence(final Charset charset) throws IOException {
    109            return new String(getByteArray(), charset);
    110        }
    111
    112        @SuppressWarnings("resource")
    113        @Override
    114        public InputStream getInputStream(final OpenOption... options) throws IOException {
    115            return BufferedFileChannelInputStream.builder().setFileChannel(origin.getChannel()).get();
    116        }
    117
    118        @Override
    119        public OutputStream getOutputStream(final OpenOption... options) throws IOException {
    120            return RandomAccessFileOutputStream.builder().setRandomAccessFile(origin).get();
    121        }
    122
    123        @Override
    124        public T getRandomAccessFile(final OpenOption... openOption) {
    125            // No conversion
    126            return get();
    127        }
    128
    129        @Override
    130        public Reader getReader(final Charset charset) throws IOException {
    131            return new InputStreamReader(getInputStream(), charset);
    132        }
    133
    134        @Override
    135        public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
    136            return new OutputStreamWriter(getOutputStream(options), charset);
    137        }
    138
    139        @Override
    140        public long size() throws IOException {
    141            return origin.length();
    142        }
    143    }
    144
    145    /**
    146     * A {@code byte[]} origin.
    147     */
    148    public static class ByteArrayOrigin extends AbstractOrigin<byte[], ByteArrayOrigin> {
    149
    150        /**
    151         * Constructs a new instance for the given origin.
    152         *
    153         * @param origin The origin.
    154         */
    155        public ByteArrayOrigin(final byte[] origin) {
    156            super(origin);
    157        }
    158
    159        @Override
    160        public byte[] getByteArray() {
    161            // No conversion
    162            return get();
    163        }
    164
    165        /**
    166         * {@inheritDoc}
    167         * <p>
    168         * The {@code options} parameter is ignored since a {@code byte[]} does not need an {@link OpenOption} to be read.
    169         * </p>
    170         */
    171        @Override
    172        public InputStream getInputStream(final OpenOption... options) throws IOException {
    173            return new ByteArrayInputStream(origin);
    174        }
    175
    176        @Override
    177        public Reader getReader(final Charset charset) throws IOException {
    178            return new InputStreamReader(getInputStream(), charset);
    179        }
    180
    181        @Override
    182        public long size() throws IOException {
    183            return origin.length;
    184        }
    185
    186    }
    187
    188    /**
    189     * A {@link CharSequence} origin.
    190     */
    191    public static class CharSequenceOrigin extends AbstractOrigin<CharSequence, CharSequenceOrigin> {
    192
    193        /**
    194         * Constructs a new instance for the given origin.
    195         *
    196         * @param origin The origin.
    197         */
    198        public CharSequenceOrigin(final CharSequence origin) {
    199            super(origin);
    200        }
    201
    202        @Override
    203        public byte[] getByteArray() {
    204            // TODO Pass in a Charset? Consider if call sites actually need this.
    205            return origin.toString().getBytes(Charset.defaultCharset());
    206        }
    207
    208        /**
    209         * {@inheritDoc}
    210         * <p>
    211         * The {@code charset} parameter is ignored since a {@link CharSequence} does not need a {@link Charset} to be read.
    212         * </p>
    213         */
    214        @Override
    215        public CharSequence getCharSequence(final Charset charset) {
    216            // No conversion
    217            return get();
    218        }
    219
    220        /**
    221         * {@inheritDoc}
    222         * <p>
    223         * The {@code options} parameter is ignored since a {@link CharSequence} does not need an {@link OpenOption} to be read.
    224         * </p>
    225         */
    226        @Override
    227        public InputStream getInputStream(final OpenOption... options) throws IOException {
    228            // TODO Pass in a Charset? Consider if call sites actually need this.
    229            return CharSequenceInputStream.builder().setCharSequence(getCharSequence(Charset.defaultCharset())).get();
    230        }
    231
    232        /**
    233         * {@inheritDoc}
    234         * <p>
    235         * The {@code charset} parameter is ignored since a {@link CharSequence} does not need a {@link Charset} to be read.
    236         * </p>
    237         */
    238        @Override
    239        public Reader getReader(final Charset charset) throws IOException {
    240            return new CharSequenceReader(get());
    241        }
    242
    243        @Override
    244        public long size() throws IOException {
    245            return origin.length();
    246        }
    247
    248    }
    249
    250    /**
    251     * A {@link File} origin.
    252     * <p>
    253     * 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.
    254     * </p>
    255     */
    256    public static class FileOrigin extends AbstractOrigin<File, FileOrigin> {
    257
    258        /**
    259         * Constructs a new instance for the given origin.
    260         *
    261         * @param origin The origin.
    262         */
    263        public FileOrigin(final File origin) {
    264            super(origin);
    265        }
    266
    267        @Override
    268        public byte[] getByteArray(final long position, final int length) throws IOException {
    269            try (RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(origin)) {
    270                return RandomAccessFiles.read(raf, position, length);
    271            }
    272        }
    273
    274        @Override
    275        public File getFile() {
    276            // No conversion
    277            return get();
    278        }
    279
    280        @Override
    281        public Path getPath() {
    282            return get().toPath();
    283        }
    284
    285    }
    286
    287    /**
    288     * An {@link InputStream} origin.
    289     * <p>
    290     * This origin cannot provide some of the other aspects.
    291     * </p>
    292     */
    293    public static class InputStreamOrigin extends AbstractOrigin<InputStream, InputStreamOrigin> {
    294
    295        /**
    296         * Constructs a new instance for the given origin.
    297         *
    298         * @param origin The origin.
    299         */
    300        public InputStreamOrigin(final InputStream origin) {
    301            super(origin);
    302        }
    303
    304        @Override
    305        public byte[] getByteArray() throws IOException {
    306            return IOUtils.toByteArray(origin);
    307        }
    308
    309        /**
    310         * {@inheritDoc}
    311         * <p>
    312         * The {@code options} parameter is ignored since a {@link InputStream} does not need an {@link OpenOption} to be read.
    313         * </p>
    314         */
    315        @Override
    316        public InputStream getInputStream(final OpenOption... options) {
    317            // No conversion
    318            return get();
    319        }
    320
    321        @Override
    322        public Reader getReader(final Charset charset) throws IOException {
    323            return new InputStreamReader(getInputStream(), charset);
    324        }
    325
    326    }
    327
    328    /**
    329     * A {@link IORandomAccessFile} origin.
    330     *
    331     * @since 2.18.0
    332     */
    333    public static class IORandomAccessFileOrigin extends AbstractRandomAccessFileOrigin<IORandomAccessFile, IORandomAccessFileOrigin> {
    334
    335        /**
    336         * A {@link RandomAccessFile} origin.
    337         *
    338         * @param origin The origin.
    339         */
    340        public IORandomAccessFileOrigin(final IORandomAccessFile origin) {
    341            super(origin);
    342        }
    343
    344        @SuppressWarnings("resource")
    345        @Override
    346        public File getFile() {
    347            return get().getFile();
    348        }
    349
    350        @Override
    351        public Path getPath() {
    352            return getFile().toPath();
    353        }
    354
    355    }
    356
    357    /**
    358     * An {@link OutputStream} origin.
    359     * <p>
    360     * This origin cannot provide some of the other aspects.
    361     * </p>
    362     */
    363    public static class OutputStreamOrigin extends AbstractOrigin<OutputStream, OutputStreamOrigin> {
    364
    365        /**
    366         * Constructs a new instance for the given origin.
    367         *
    368         * @param origin The origin.
    369         */
    370        public OutputStreamOrigin(final OutputStream origin) {
    371            super(origin);
    372        }
    373
    374        /**
    375         * {@inheritDoc}
    376         * <p>
    377         * The {@code options} parameter is ignored since a {@link OutputStream} does not need an {@link OpenOption} to be written.
    378         * </p>
    379         */
    380        @Override
    381        public OutputStream getOutputStream(final OpenOption... options) {
    382            // No conversion
    383            return get();
    384        }
    385
    386        /**
    387         * {@inheritDoc}
    388         * <p>
    389         * The {@code options} parameter is ignored since a {@link OutputStream} does not need an {@link OpenOption} to be written.
    390         * </p>
    391         */
    392        @Override
    393        public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
    394            return new OutputStreamWriter(origin, charset);
    395        }
    396    }
    397
    398    /**
    399     * A {@link Path} origin.
    400     * <p>
    401     * 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.
    402     * </p>
    403     */
    404    public static class PathOrigin extends AbstractOrigin<Path, PathOrigin> {
    405
    406        /**
    407         * Constructs a new instance for the given origin.
    408         *
    409         * @param origin The origin.
    410         */
    411        public PathOrigin(final Path origin) {
    412            super(origin);
    413        }
    414
    415        @Override
    416        public byte[] getByteArray(final long position, final int length) throws IOException {
    417            return RandomAccessFileMode.READ_ONLY.apply(origin, raf -> RandomAccessFiles.read(raf, position, length));
    418        }
    419
    420        @Override
    421        public File getFile() {
    422            return get().toFile();
    423        }
    424
    425        @Override
    426        public Path getPath() {
    427            // No conversion
    428            return get();
    429        }
    430
    431    }
    432
    433    /**
    434     * A {@link RandomAccessFile} origin.
    435     * <p>
    436     * This origin cannot support File and Path since you cannot query a RandomAccessFile for those attributes; Use {@link IORandomAccessFileOrigin}
    437     * instead.
    438     * </p>
    439     */
    440    public static class RandomAccessFileOrigin extends AbstractRandomAccessFileOrigin<RandomAccessFile, RandomAccessFileOrigin> {
    441
    442        /**
    443         * A {@link RandomAccessFile} origin.
    444         * <p>
    445         * Starting from this origin, you can everything except a Path and a File.
    446         * </p>
    447         *
    448         * @param origin The origin.
    449         */
    450        public RandomAccessFileOrigin(final RandomAccessFile origin) {
    451            super(origin);
    452        }
    453
    454    }
    455
    456    /**
    457     * A {@link Reader} origin.
    458     * <p>
    459     * This origin cannot provide conversions to other aspects.
    460     * </p>
    461     */
    462    public static class ReaderOrigin extends AbstractOrigin<Reader, ReaderOrigin> {
    463
    464        /**
    465         * Constructs a new instance for the given origin.
    466         *
    467         * @param origin The origin.
    468         */
    469        public ReaderOrigin(final Reader origin) {
    470            super(origin);
    471        }
    472
    473        @Override
    474        public byte[] getByteArray() throws IOException {
    475            // TODO Pass in a Charset? Consider if call sites actually need this.
    476            return IOUtils.toByteArray(origin, Charset.defaultCharset());
    477        }
    478
    479        /**
    480         * {@inheritDoc}
    481         * <p>
    482         * The {@code charset} parameter is ignored since a {@link Reader} does not need a {@link Charset} to be read.
    483         * </p>
    484         */
    485        @Override
    486        public CharSequence getCharSequence(final Charset charset) throws IOException {
    487            return IOUtils.toString(origin);
    488        }
    489
    490        /**
    491         * {@inheritDoc}
    492         * <p>
    493         * The {@code options} parameter is ignored since a {@link Reader} does not need an {@link OpenOption} to be read.
    494         * </p>
    495         */
    496        @Override
    497        public InputStream getInputStream(final OpenOption... options) throws IOException {
    498            // TODO Pass in a Charset? Consider if call sites actually need this.
    499            return ReaderInputStream.builder().setReader(origin).setCharset(Charset.defaultCharset()).get();
    500        }
    501
    502        /**
    503         * {@inheritDoc}
    504         * <p>
    505         * The {@code charset} parameter is ignored since a {@link Reader} does not need a {@link Charset} to be read.
    506         * </p>
    507         */
    508        @Override
    509        public Reader getReader(final Charset charset) throws IOException {
    510            // No conversion
    511            return get();
    512        }
    513    }
    514
    515    /**
    516     * A {@link URI} origin.
    517     */
    518    public static class URIOrigin extends AbstractOrigin<URI, URIOrigin> {
    519
    520        private static final String SCHEME_HTTPS = "https";
    521        private static final String SCHEME_HTTP = "http";
    522
    523        /**
    524         * Constructs a new instance for the given origin.
    525         *
    526         * @param origin The origin.
    527         */
    528        public URIOrigin(final URI origin) {
    529            super(origin);
    530        }
    531
    532        @Override
    533        public File getFile() {
    534            return getPath().toFile();
    535        }
    536
    537        @Override
    538        public InputStream getInputStream(final OpenOption... options) throws IOException {
    539            final URI uri = get();
    540            final String scheme = uri.getScheme();
    541            final FileSystemProvider fileSystemProvider = FileSystemProviders.installed().getFileSystemProvider(scheme);
    542            if (fileSystemProvider != null) {
    543                return Files.newInputStream(fileSystemProvider.getPath(uri), options);
    544            }
    545            if (SCHEME_HTTP.equalsIgnoreCase(scheme) || SCHEME_HTTPS.equalsIgnoreCase(scheme)) {
    546                return uri.toURL().openStream();
    547            }
    548            return Files.newInputStream(getPath(), options);
    549        }
    550
    551        @Override
    552        public Path getPath() {
    553            return Paths.get(get());
    554        }
    555    }
    556
    557    /**
    558     * A {@link Writer} origin.
    559     * <p>
    560     * This origin cannot provide conversions to other aspects.
    561     * </p>
    562     */
    563    public static class WriterOrigin extends AbstractOrigin<Writer, WriterOrigin> {
    564
    565        /**
    566         * Constructs a new instance for the given origin.
    567         *
    568         * @param origin The origin.
    569         */
    570        public WriterOrigin(final Writer origin) {
    571            super(origin);
    572        }
    573
    574        /**
    575         * {@inheritDoc}
    576         * <p>
    577         * The {@code options} parameter is ignored since a {@link Writer} does not need an {@link OpenOption} to be written.
    578         * </p>
    579         */
    580        @Override
    581        public OutputStream getOutputStream(final OpenOption... options) throws IOException {
    582            // TODO Pass in a Charset? Consider if call sites actually need this.
    583            return WriterOutputStream.builder().setWriter(origin).setCharset(Charset.defaultCharset()).get();
    584        }
    585
    586        /**
    587         * {@inheritDoc}
    588         * <p>
    589         * The {@code charset} parameter is ignored since a {@link Writer} does not need a {@link Charset} to be written.
    590         * </p>
    591         * <p>
    592         * The {@code options} parameter is ignored since a {@link Writer} does not need an {@link OpenOption} to be written.
    593         * </p>
    594         */
    595        @Override
    596        public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
    597            // No conversion
    598            return get();
    599        }
    600    }
    601
    602    /**
    603     * The non-null origin.
    604     */
    605    final T origin;
    606
    607    /**
    608     * Constructs a new instance for a subclass.
    609     *
    610     * @param origin The origin.
    611     */
    612    protected AbstractOrigin(final T origin) {
    613        this.origin = Objects.requireNonNull(origin, "origin");
    614    }
    615
    616    /**
    617     * Gets the origin.
    618     *
    619     * @return the origin.
    620     */
    621    @Override
    622    public T get() {
    623        return origin;
    624    }
    625
    626    /**
    627     * Gets this origin as a byte array, if possible.
    628     *
    629     * @return this origin as a byte array, if possible.
    630     * @throws IOException                   if an I/O error occurs.
    631     * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
    632     */
    633    public byte[] getByteArray() throws IOException {
    634        return Files.readAllBytes(getPath());
    635    }
    636
    637    /**
    638     * Gets a portion of this origin as a byte array, if possible.
    639     *
    640     * @param position the initial index of the range to be copied, inclusive.
    641     * @param length   How many bytes to copy.
    642     * @return this origin as a byte array, if possible.
    643     * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
    644     * @throws ArithmeticException           if the {@code position} overflows an int
    645     * @throws IOException                   if an I/O error occurs.
    646     * @since 2.13.0
    647     */
    648    public byte[] getByteArray(final long position, final int length) throws IOException {
    649        final byte[] bytes = getByteArray();
    650        // Checks for int overflow.
    651        final int start = Math.toIntExact(position);
    652        if (start < 0 || length < 0 || start + length < 0 || start + length > bytes.length) {
    653            throw new IllegalArgumentException("Couldn't read array (start: " + start + ", length: " + length + ", data length: " + bytes.length + ").");
    654        }
    655        return Arrays.copyOfRange(bytes, start, start + length);
    656    }
    657
    658    /**
    659     * Gets this origin as a byte array, if possible.
    660     *
    661     * @param charset The charset to use if conversion from bytes is needed.
    662     * @return this origin as a byte array, if possible.
    663     * @throws IOException                   if an I/O error occurs.
    664     * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
    665     */
    666    public CharSequence getCharSequence(final Charset charset) throws IOException {
    667        return new String(getByteArray(), charset);
    668    }
    669
    670    /**
    671     * Gets this origin as a Path, if possible.
    672     *
    673     * @return this origin as a Path, if possible.
    674     * @throws UnsupportedOperationException if this method is not implemented in a concrete subclass.
    675     */
    676    public File getFile() {
    677        throw new UnsupportedOperationException(
    678                String.format("%s#getFile() for %s origin %s", getSimpleClassName(), origin.getClass().getSimpleName(), origin));
    679    }
    680
    681    /**
    682     * Gets this origin as an InputStream, if possible.
    683     *
    684     * @param options options specifying how the file is opened
    685     * @return this origin as an InputStream, if possible.
    686     * @throws IOException                   if an I/O error occurs.
    687     * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
    688     */
    689    public InputStream getInputStream(final OpenOption... options) throws IOException {
    690        return Files.newInputStream(getPath(), options);
    691    }
    692
    693    /**
    694     * Gets this origin as an OutputStream, if possible.
    695     *
    696     * @param options options specifying how the file is opened
    697     * @return this origin as an OutputStream, if possible.
    698     * @throws IOException                   if an I/O error occurs.
    699     * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
    700     */
    701    public OutputStream getOutputStream(final OpenOption... options) throws IOException {
    702        return Files.newOutputStream(getPath(), options);
    703    }
    704
    705    /**
    706     * Gets this origin as a Path, if possible.
    707     *
    708     * @return this origin as a Path, if possible.
    709     * @throws UnsupportedOperationException if this method is not implemented in a concrete subclass.
    710     */
    711    public Path getPath() {
    712        throw new UnsupportedOperationException(
    713                String.format("%s#getPath() for %s origin %s", getSimpleClassName(), origin.getClass().getSimpleName(), origin));
    714    }
    715
    716    /**
    717     * Gets this origin as a RandomAccessFile, if possible.
    718     *
    719     * @param openOption options like {@link StandardOpenOption}.
    720     * @return this origin as a RandomAccessFile, if possible.
    721     * @throws FileNotFoundException         See {@link RandomAccessFile#RandomAccessFile(File, String)}.
    722     * @throws UnsupportedOperationException if this method is not implemented in a concrete subclass.
    723     * @since 2.18.0
    724     */
    725    public RandomAccessFile getRandomAccessFile(final OpenOption... openOption) throws FileNotFoundException {
    726        return RandomAccessFileMode.valueOf(openOption).create(getFile());
    727    }
    728
    729    /**
    730     * Gets a new Reader on the origin, buffered by default.
    731     *
    732     * @param charset the charset to use for decoding
    733     * @return a new Reader on the origin.
    734     * @throws IOException if an I/O error occurs opening the file.
    735     */
    736    public Reader getReader(final Charset charset) throws IOException {
    737        return Files.newBufferedReader(getPath(), charset);
    738    }
    739
    740    private String getSimpleClassName() {
    741        return getClass().getSimpleName();
    742    }
    743
    744    /**
    745     * Gets a new Writer on the origin, buffered by default.
    746     *
    747     * @param charset the charset to use for encoding
    748     * @param options options specifying how the file is opened
    749     * @return a new Writer on the origin.
    750     * @throws IOException                   if an I/O error occurs opening or creating the file.
    751     * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
    752     */
    753    public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
    754        return Files.newBufferedWriter(getPath(), charset, options);
    755    }
    756
    757    /**
    758     * Gets the size of the origin, if possible.
    759     *
    760     * @return the size of the origin in bytes or characters.
    761     * @throws IOException if an I/O error occurs.
    762     * @since 2.13.0
    763     */
    764    public long size() throws IOException {
    765        return Files.size(getPath());
    766    }
    767
    768    @Override
    769    public String toString() {
    770        return getSimpleClassName() + "[" + origin.toString() + "]";
    771    }
    772}