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