AbstractOrigin.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      https://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.io.build;

  18. import java.io.ByteArrayInputStream;
  19. import java.io.File;
  20. import java.io.FileNotFoundException;
  21. import java.io.IOException;
  22. import java.io.InputStream;
  23. import java.io.InputStreamReader;
  24. import java.io.OutputStream;
  25. import java.io.OutputStreamWriter;
  26. import java.io.RandomAccessFile;
  27. import java.io.Reader;
  28. import java.io.Writer;
  29. import java.net.URI;
  30. import java.nio.charset.Charset;
  31. import java.nio.file.Files;
  32. import java.nio.file.OpenOption;
  33. import java.nio.file.Path;
  34. import java.nio.file.Paths;
  35. import java.nio.file.StandardOpenOption;
  36. import java.nio.file.spi.FileSystemProvider;
  37. import java.util.Arrays;
  38. import java.util.Objects;

  39. import org.apache.commons.io.Charsets;
  40. import org.apache.commons.io.IORandomAccessFile;
  41. import org.apache.commons.io.IOUtils;
  42. import org.apache.commons.io.RandomAccessFileMode;
  43. import org.apache.commons.io.RandomAccessFiles;
  44. import org.apache.commons.io.file.spi.FileSystemProviders;
  45. import org.apache.commons.io.input.BufferedFileChannelInputStream;
  46. import org.apache.commons.io.input.CharSequenceInputStream;
  47. import org.apache.commons.io.input.CharSequenceReader;
  48. import org.apache.commons.io.input.ReaderInputStream;
  49. import org.apache.commons.io.output.RandomAccessFileOutputStream;
  50. import org.apache.commons.io.output.WriterOutputStream;

  51. /**
  52.  * Abstracts the origin of data for builders like a {@link File}, {@link Path}, {@link Reader}, {@link Writer}, {@link InputStream}, {@link OutputStream}, and
  53.  * {@link URI}.
  54.  * <p>
  55.  * Some methods may throw {@link UnsupportedOperationException} if that method is not implemented in a concrete subclass, see {@link #getFile()} and
  56.  * {@link #getPath()}.
  57.  * </p>
  58.  *
  59.  * @param <T> the type of instances to build.
  60.  * @param <B> the type of builder subclass.
  61.  * @since 2.12.0
  62.  */
  63. public abstract class AbstractOrigin<T, B extends AbstractOrigin<T, B>> extends AbstractSupplier<T, B> {

  64.     /**
  65.      * A {@link RandomAccessFile} origin.
  66.      * <p>
  67.      * This origin cannot support File and Path since you cannot query a RandomAccessFile for those attributes; Use {@link IORandomAccessFileOrigin}
  68.      * instead.
  69.      * </p>
  70.      *
  71.      * @param <T> the type of instances to build.
  72.      * @param <B> the type of builder subclass.
  73.      */
  74.     public abstract static class AbstractRandomAccessFileOrigin<T extends RandomAccessFile, B extends AbstractRandomAccessFileOrigin<T, B>>
  75.             extends AbstractOrigin<T, B> {

  76.         /**
  77.          * A {@link RandomAccessFile} origin.
  78.          * <p>
  79.          * Starting from this origin, you can everything except a Path and a File.
  80.          * </p>
  81.          *
  82.          * @param origin The origin, not null.
  83.          */
  84.         public AbstractRandomAccessFileOrigin(final T origin) {
  85.             super(origin);
  86.         }

  87.         @Override
  88.         public byte[] getByteArray() throws IOException {
  89.             final long longLen = origin.length();
  90.             if (longLen > Integer.MAX_VALUE) {
  91.                 throw new IllegalStateException("Origin too large.");
  92.             }
  93.             return RandomAccessFiles.read(origin, 0, (int) longLen);
  94.         }

  95.         @Override
  96.         public byte[] getByteArray(final long position, final int length) throws IOException {
  97.             return RandomAccessFiles.read(origin, position, length);
  98.         }

  99.         @Override
  100.         public CharSequence getCharSequence(final Charset charset) throws IOException {
  101.             return new String(getByteArray(), charset);
  102.         }

  103.         @SuppressWarnings("resource")
  104.         @Override
  105.         public InputStream getInputStream(final OpenOption... options) throws IOException {
  106.             return BufferedFileChannelInputStream.builder().setFileChannel(origin.getChannel()).get();
  107.         }

  108.         @Override
  109.         public OutputStream getOutputStream(final OpenOption... options) throws IOException {
  110.             return RandomAccessFileOutputStream.builder().setRandomAccessFile(origin).get();
  111.         }

  112.         @Override
  113.         public T getRandomAccessFile(final OpenOption... openOption) {
  114.             // No conversion
  115.             return get();
  116.         }

  117.         @Override
  118.         public Reader getReader(final Charset charset) throws IOException {
  119.             return new InputStreamReader(getInputStream(), Charsets.toCharset(charset));
  120.         }

  121.         @Override
  122.         public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
  123.             return new OutputStreamWriter(getOutputStream(options), Charsets.toCharset(charset));
  124.         }

  125.         @Override
  126.         public long size() throws IOException {
  127.             return origin.length();
  128.         }
  129.     }

  130.     /**
  131.      * A {@code byte[]} origin.
  132.      */
  133.     public static class ByteArrayOrigin extends AbstractOrigin<byte[], ByteArrayOrigin> {

  134.         /**
  135.          * Constructs a new instance for the given origin.
  136.          *
  137.          * @param origin The origin, not null.
  138.          */
  139.         public ByteArrayOrigin(final byte[] origin) {
  140.             super(origin);
  141.         }

  142.         @Override
  143.         public byte[] getByteArray() {
  144.             // No conversion
  145.             return get();
  146.         }

  147.         /**
  148.          * {@inheritDoc}
  149.          * <p>
  150.          * The {@code options} parameter is ignored since a {@code byte[]} does not need an {@link OpenOption} to be read.
  151.          * </p>
  152.          */
  153.         @Override
  154.         public InputStream getInputStream(final OpenOption... options) throws IOException {
  155.             return new ByteArrayInputStream(origin);
  156.         }

  157.         @Override
  158.         public Reader getReader(final Charset charset) throws IOException {
  159.             return new InputStreamReader(getInputStream(), Charsets.toCharset(charset));
  160.         }

  161.         @Override
  162.         public long size() throws IOException {
  163.             return origin.length;
  164.         }

  165.     }

  166.     /**
  167.      * A {@link CharSequence} origin.
  168.      */
  169.     public static class CharSequenceOrigin extends AbstractOrigin<CharSequence, CharSequenceOrigin> {

  170.         /**
  171.          * Constructs a new instance for the given origin.
  172.          *
  173.          * @param origin The origin, not null.
  174.          */
  175.         public CharSequenceOrigin(final CharSequence origin) {
  176.             super(origin);
  177.         }

  178.         @Override
  179.         public byte[] getByteArray() {
  180.             // TODO Pass in a Charset? Consider if call sites actually need this.
  181.             return origin.toString().getBytes(Charset.defaultCharset());
  182.         }

  183.         /**
  184.          * {@inheritDoc}
  185.          * <p>
  186.          * The {@code charset} parameter is ignored since a {@link CharSequence} does not need a {@link Charset} to be read.
  187.          * </p>
  188.          */
  189.         @Override
  190.         public CharSequence getCharSequence(final Charset charset) {
  191.             // No conversion
  192.             return get();
  193.         }

  194.         /**
  195.          * {@inheritDoc}
  196.          * <p>
  197.          * The {@code options} parameter is ignored since a {@link CharSequence} does not need an {@link OpenOption} to be read.
  198.          * </p>
  199.          */
  200.         @Override
  201.         public InputStream getInputStream(final OpenOption... options) throws IOException {
  202.             // TODO Pass in a Charset? Consider if call sites actually need this.
  203.             return CharSequenceInputStream.builder().setCharSequence(getCharSequence(Charset.defaultCharset())).get();
  204.         }

  205.         /**
  206.          * {@inheritDoc}
  207.          * <p>
  208.          * The {@code charset} parameter is ignored since a {@link CharSequence} does not need a {@link Charset} to be read.
  209.          * </p>
  210.          */
  211.         @Override
  212.         public Reader getReader(final Charset charset) throws IOException {
  213.             return new CharSequenceReader(get());
  214.         }

  215.         @Override
  216.         public long size() throws IOException {
  217.             return origin.length();
  218.         }

  219.     }

  220.     /**
  221.      * A {@link File} origin.
  222.      * <p>
  223.      * 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.
  224.      * </p>
  225.      */
  226.     public static class FileOrigin extends AbstractOrigin<File, FileOrigin> {

  227.         /**
  228.          * Constructs a new instance for the given origin.
  229.          *
  230.          * @param origin The origin, not null.
  231.          */
  232.         public FileOrigin(final File origin) {
  233.             super(origin);
  234.         }

  235.         @Override
  236.         public byte[] getByteArray(final long position, final int length) throws IOException {
  237.             try (RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(origin)) {
  238.                 return RandomAccessFiles.read(raf, position, length);
  239.             }
  240.         }

  241.         @Override
  242.         public File getFile() {
  243.             // No conversion
  244.             return get();
  245.         }

  246.         @Override
  247.         public Path getPath() {
  248.             return get().toPath();
  249.         }

  250.     }

  251.     /**
  252.      * An {@link InputStream} origin.
  253.      * <p>
  254.      * This origin cannot provide some of the other aspects.
  255.      * </p>
  256.      */
  257.     public static class InputStreamOrigin extends AbstractOrigin<InputStream, InputStreamOrigin> {

  258.         /**
  259.          * Constructs a new instance for the given origin.
  260.          *
  261.          * @param origin The origin, not null.
  262.          */
  263.         public InputStreamOrigin(final InputStream origin) {
  264.             super(origin);
  265.         }

  266.         @Override
  267.         public byte[] getByteArray() throws IOException {
  268.             return IOUtils.toByteArray(origin);
  269.         }

  270.         /**
  271.          * {@inheritDoc}
  272.          * <p>
  273.          * The {@code options} parameter is ignored since a {@link InputStream} does not need an {@link OpenOption} to be read.
  274.          * </p>
  275.          */
  276.         @Override
  277.         public InputStream getInputStream(final OpenOption... options) {
  278.             // No conversion
  279.             return get();
  280.         }

  281.         @Override
  282.         public Reader getReader(final Charset charset) throws IOException {
  283.             return new InputStreamReader(getInputStream(), Charsets.toCharset(charset));
  284.         }

  285.     }

  286.     /**
  287.      * A {@link IORandomAccessFile} origin.
  288.      *
  289.      * @since 2.18.0
  290.      */
  291.     public static class IORandomAccessFileOrigin extends AbstractRandomAccessFileOrigin<IORandomAccessFile, IORandomAccessFileOrigin> {

  292.         /**
  293.          * A {@link RandomAccessFile} origin.
  294.          *
  295.          * @param origin The origin, not null.
  296.          */
  297.         public IORandomAccessFileOrigin(final IORandomAccessFile origin) {
  298.             super(origin);
  299.         }

  300.         @SuppressWarnings("resource")
  301.         @Override
  302.         public File getFile() {
  303.             return get().getFile();
  304.         }

  305.         @Override
  306.         public Path getPath() {
  307.             return getFile().toPath();
  308.         }

  309.     }

  310.     /**
  311.      * An {@link OutputStream} origin.
  312.      * <p>
  313.      * This origin cannot provide some of the other aspects.
  314.      * </p>
  315.      */
  316.     public static class OutputStreamOrigin extends AbstractOrigin<OutputStream, OutputStreamOrigin> {

  317.         /**
  318.          * Constructs a new instance for the given origin.
  319.          *
  320.          * @param origin The origin, not null.
  321.          */
  322.         public OutputStreamOrigin(final OutputStream origin) {
  323.             super(origin);
  324.         }

  325.         /**
  326.          * {@inheritDoc}
  327.          * <p>
  328.          * The {@code options} parameter is ignored since a {@link OutputStream} does not need an {@link OpenOption} to be written.
  329.          * </p>
  330.          */
  331.         @Override
  332.         public OutputStream getOutputStream(final OpenOption... options) {
  333.             // No conversion
  334.             return get();
  335.         }

  336.         /**
  337.          * {@inheritDoc}
  338.          * <p>
  339.          * The {@code options} parameter is ignored since a {@link OutputStream} does not need an {@link OpenOption} to be written.
  340.          * </p>
  341.          */
  342.         @Override
  343.         public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
  344.             return new OutputStreamWriter(origin, Charsets.toCharset(charset));
  345.         }
  346.     }

  347.     /**
  348.      * A {@link Path} origin.
  349.      * <p>
  350.      * 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.
  351.      * </p>
  352.      */
  353.     public static class PathOrigin extends AbstractOrigin<Path, PathOrigin> {

  354.         /**
  355.          * Constructs a new instance for the given origin.
  356.          *
  357.          * @param origin The origin, not null.
  358.          */
  359.         public PathOrigin(final Path origin) {
  360.             super(origin);
  361.         }

  362.         @Override
  363.         public byte[] getByteArray(final long position, final int length) throws IOException {
  364.             return RandomAccessFileMode.READ_ONLY.apply(origin, raf -> RandomAccessFiles.read(raf, position, length));
  365.         }

  366.         @Override
  367.         public File getFile() {
  368.             return get().toFile();
  369.         }

  370.         @Override
  371.         public Path getPath() {
  372.             // No conversion
  373.             return get();
  374.         }

  375.     }

  376.     /**
  377.      * A {@link RandomAccessFile} origin.
  378.      * <p>
  379.      * This origin cannot support File and Path since you cannot query a RandomAccessFile for those attributes; Use {@link IORandomAccessFileOrigin}
  380.      * instead.
  381.      * </p>
  382.      */
  383.     public static class RandomAccessFileOrigin extends AbstractRandomAccessFileOrigin<RandomAccessFile, RandomAccessFileOrigin> {

  384.         /**
  385.          * A {@link RandomAccessFile} origin.
  386.          * <p>
  387.          * Starting from this origin, you can everything except a Path and a File.
  388.          * </p>
  389.          *
  390.          * @param origin The origin, not null.
  391.          */
  392.         public RandomAccessFileOrigin(final RandomAccessFile origin) {
  393.             super(origin);
  394.         }

  395.     }

  396.     /**
  397.      * A {@link Reader} origin.
  398.      * <p>
  399.      * This origin cannot provide conversions to other aspects.
  400.      * </p>
  401.      */
  402.     public static class ReaderOrigin extends AbstractOrigin<Reader, ReaderOrigin> {

  403.         /**
  404.          * Constructs a new instance for the given origin.
  405.          *
  406.          * @param origin The origin, not null.
  407.          */
  408.         public ReaderOrigin(final Reader origin) {
  409.             super(origin);
  410.         }

  411.         @Override
  412.         public byte[] getByteArray() throws IOException {
  413.             // TODO Pass in a Charset? Consider if call sites actually need this.
  414.             return IOUtils.toByteArray(origin, Charset.defaultCharset());
  415.         }

  416.         /**
  417.          * {@inheritDoc}
  418.          * <p>
  419.          * The {@code charset} parameter is ignored since a {@link Reader} does not need a {@link Charset} to be read.
  420.          * </p>
  421.          */
  422.         @Override
  423.         public CharSequence getCharSequence(final Charset charset) throws IOException {
  424.             return IOUtils.toString(origin);
  425.         }

  426.         /**
  427.          * {@inheritDoc}
  428.          * <p>
  429.          * The {@code options} parameter is ignored since a {@link Reader} does not need an {@link OpenOption} to be read.
  430.          * </p>
  431.          */
  432.         @Override
  433.         public InputStream getInputStream(final OpenOption... options) throws IOException {
  434.             // TODO Pass in a Charset? Consider if call sites actually need this.
  435.             return ReaderInputStream.builder().setReader(origin).setCharset(Charset.defaultCharset()).get();
  436.         }

  437.         /**
  438.          * {@inheritDoc}
  439.          * <p>
  440.          * The {@code charset} parameter is ignored since a {@link Reader} does not need a {@link Charset} to be read.
  441.          * </p>
  442.          */
  443.         @Override
  444.         public Reader getReader(final Charset charset) throws IOException {
  445.             // No conversion
  446.             return get();
  447.         }
  448.     }

  449.     /**
  450.      * A {@link URI} origin.
  451.      */
  452.     public static class URIOrigin extends AbstractOrigin<URI, URIOrigin> {

  453.         private static final String SCHEME_HTTPS = "https";
  454.         private static final String SCHEME_HTTP = "http";

  455.         /**
  456.          * Constructs a new instance for the given origin.
  457.          *
  458.          * @param origin The origin, not null.
  459.          */
  460.         public URIOrigin(final URI origin) {
  461.             super(origin);
  462.         }

  463.         @Override
  464.         public File getFile() {
  465.             return getPath().toFile();
  466.         }

  467.         @Override
  468.         public InputStream getInputStream(final OpenOption... options) throws IOException {
  469.             final URI uri = get();
  470.             final String scheme = uri.getScheme();
  471.             final FileSystemProvider fileSystemProvider = FileSystemProviders.installed().getFileSystemProvider(scheme);
  472.             if (fileSystemProvider != null) {
  473.                 return Files.newInputStream(fileSystemProvider.getPath(uri), options);
  474.             }
  475.             if (SCHEME_HTTP.equalsIgnoreCase(scheme) || SCHEME_HTTPS.equalsIgnoreCase(scheme)) {
  476.                 return uri.toURL().openStream();
  477.             }
  478.             return Files.newInputStream(getPath(), options);
  479.         }

  480.         @Override
  481.         public Path getPath() {
  482.             return Paths.get(get());
  483.         }
  484.     }

  485.     /**
  486.      * A {@link Writer} origin.
  487.      * <p>
  488.      * This origin cannot provide conversions to other aspects.
  489.      * </p>
  490.      */
  491.     public static class WriterOrigin extends AbstractOrigin<Writer, WriterOrigin> {

  492.         /**
  493.          * Constructs a new instance for the given origin.
  494.          *
  495.          * @param origin The origin, not null.
  496.          */
  497.         public WriterOrigin(final Writer origin) {
  498.             super(origin);
  499.         }

  500.         /**
  501.          * {@inheritDoc}
  502.          * <p>
  503.          * The {@code options} parameter is ignored since a {@link Writer} does not need an {@link OpenOption} to be written.
  504.          * </p>
  505.          */
  506.         @Override
  507.         public OutputStream getOutputStream(final OpenOption... options) throws IOException {
  508.             // TODO Pass in a Charset? Consider if call sites actually need this.
  509.             return WriterOutputStream.builder().setWriter(origin).setCharset(Charset.defaultCharset()).get();
  510.         }

  511.         /**
  512.          * {@inheritDoc}
  513.          * <p>
  514.          * The {@code charset} parameter is ignored since a {@link Writer} does not need a {@link Charset} to be written.
  515.          * </p>
  516.          * <p>
  517.          * The {@code options} parameter is ignored since a {@link Writer} does not need an {@link OpenOption} to be written.
  518.          * </p>
  519.          */
  520.         @Override
  521.         public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
  522.             // No conversion
  523.             return get();
  524.         }
  525.     }

  526.     /**
  527.      * The non-null origin.
  528.      */
  529.     final T origin;

  530.     /**
  531.      * Constructs a new instance for subclasses.
  532.      *
  533.      * @param origin The origin, not null.
  534.      */
  535.     protected AbstractOrigin(final T origin) {
  536.         this.origin = Objects.requireNonNull(origin, "origin");
  537.     }

  538.     /**
  539.      * Gets the origin.
  540.      *
  541.      * @return the origin.
  542.      */
  543.     @Override
  544.     public T get() {
  545.         return origin;
  546.     }

  547.     /**
  548.      * Gets this origin as a byte array, if possible.
  549.      *
  550.      * @return this origin as a byte array, if possible.
  551.      * @throws IOException                   if an I/O error occurs.
  552.      * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
  553.      */
  554.     public byte[] getByteArray() throws IOException {
  555.         return Files.readAllBytes(getPath());
  556.     }

  557.     /**
  558.      * Gets a portion of this origin as a byte array, if possible.
  559.      *
  560.      * @param position the initial index of the range to be copied, inclusive.
  561.      * @param length   How many bytes to copy.
  562.      * @return this origin as a byte array, if possible.
  563.      * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
  564.      * @throws ArithmeticException           if the {@code position} overflows an int
  565.      * @throws IOException                   if an I/O error occurs.
  566.      * @since 2.13.0
  567.      */
  568.     public byte[] getByteArray(final long position, final int length) throws IOException {
  569.         final byte[] bytes = getByteArray();
  570.         // Checks for int overflow.
  571.         final int start = Math.toIntExact(position);
  572.         if (start < 0 || length < 0 || start + length < 0 || start + length > bytes.length) {
  573.             throw new IllegalArgumentException("Couldn't read array (start: " + start + ", length: " + length + ", data length: " + bytes.length + ").");
  574.         }
  575.         return Arrays.copyOfRange(bytes, start, start + length);
  576.     }

  577.     /**
  578.      * Gets this origin as a byte array, if possible.
  579.      *
  580.      * @param charset The charset to use if conversion from bytes is needed.
  581.      * @return this origin as a byte array, if possible.
  582.      * @throws IOException                   if an I/O error occurs.
  583.      * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
  584.      */
  585.     public CharSequence getCharSequence(final Charset charset) throws IOException {
  586.         return new String(getByteArray(), charset);
  587.     }

  588.     /**
  589.      * Gets this origin as a Path, if possible.
  590.      *
  591.      * @return this origin as a Path, if possible.
  592.      * @throws UnsupportedOperationException if this method is not implemented in a concrete subclass.
  593.      */
  594.     public File getFile() {
  595.         throw new UnsupportedOperationException(
  596.                 String.format("%s#getFile() for %s origin %s", getSimpleClassName(), origin.getClass().getSimpleName(), origin));
  597.     }

  598.     /**
  599.      * Gets this origin as an InputStream, if possible.
  600.      *
  601.      * @param options options specifying how the file is opened
  602.      * @return this origin as an InputStream, if possible.
  603.      * @throws IOException                   if an I/O error occurs.
  604.      * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
  605.      */
  606.     public InputStream getInputStream(final OpenOption... options) throws IOException {
  607.         return Files.newInputStream(getPath(), options);
  608.     }

  609.     /**
  610.      * Gets this origin as an OutputStream, if possible.
  611.      *
  612.      * @param options options specifying how the file is opened
  613.      * @return this origin as an OutputStream, if possible.
  614.      * @throws IOException                   if an I/O error occurs.
  615.      * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
  616.      */
  617.     public OutputStream getOutputStream(final OpenOption... options) throws IOException {
  618.         return Files.newOutputStream(getPath(), options);
  619.     }

  620.     /**
  621.      * Gets this origin as a Path, if possible.
  622.      *
  623.      * @return this origin as a Path, if possible.
  624.      * @throws UnsupportedOperationException if this method is not implemented in a concrete subclass.
  625.      */
  626.     public Path getPath() {
  627.         throw new UnsupportedOperationException(
  628.                 String.format("%s#getPath() for %s origin %s", getSimpleClassName(), origin.getClass().getSimpleName(), origin));
  629.     }

  630.     /**
  631.      * Gets this origin as a RandomAccessFile, if possible.
  632.      *
  633.      * @param openOption options like {@link StandardOpenOption}.
  634.      * @return this origin as a RandomAccessFile, if possible.
  635.      * @throws FileNotFoundException         See {@link RandomAccessFile#RandomAccessFile(File, String)}.
  636.      * @throws UnsupportedOperationException if this method is not implemented in a concrete subclass.
  637.      * @since 2.18.0
  638.      */
  639.     public RandomAccessFile getRandomAccessFile(final OpenOption... openOption) throws FileNotFoundException {
  640.         return RandomAccessFileMode.valueOf(openOption).create(getFile());
  641.     }

  642.     /**
  643.      * Gets a new Reader on the origin, buffered by default.
  644.      *
  645.      * @param charset the charset to use for decoding, null maps to the default Charset.
  646.      * @return a new Reader on the origin.
  647.      * @throws IOException if an I/O error occurs opening the file.
  648.      */
  649.     public Reader getReader(final Charset charset) throws IOException {
  650.         return Files.newBufferedReader(getPath(), Charsets.toCharset(charset));
  651.     }

  652.     private String getSimpleClassName() {
  653.         return getClass().getSimpleName();
  654.     }

  655.     /**
  656.      * Gets a new Writer on the origin, buffered by default.
  657.      *
  658.      * @param charset the charset to use for encoding
  659.      * @param options options specifying how the file is opened
  660.      * @return a new Writer on the origin.
  661.      * @throws IOException                   if an I/O error occurs opening or creating the file.
  662.      * @throws UnsupportedOperationException if the origin cannot be converted to a Path.
  663.      */
  664.     public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
  665.         return Files.newBufferedWriter(getPath(), Charsets.toCharset(charset), options);
  666.     }

  667.     /**
  668.      * Gets the size of the origin, if possible.
  669.      *
  670.      * @return the size of the origin in bytes or characters.
  671.      * @throws IOException if an I/O error occurs.
  672.      * @since 2.13.0
  673.      */
  674.     public long size() throws IOException {
  675.         return Files.size(getPath());
  676.     }

  677.     @Override
  678.     public String toString() {
  679.         return getSimpleClassName() + "[" + origin.toString() + "]";
  680.     }
  681. }