RandomAccessFileInputStream.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.input;

  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.io.OutputStream;
  21. import java.io.RandomAccessFile;
  22. import java.util.Objects;

  23. import org.apache.commons.io.IOUtils;
  24. import org.apache.commons.io.build.AbstractOrigin;
  25. import org.apache.commons.io.build.AbstractStreamBuilder;

  26. /**
  27.  * Streams data from a {@link RandomAccessFile} starting at its current position.
  28.  * <p>
  29.  * To build an instance, use {@link Builder}.
  30.  * </p>
  31.  *
  32.  * @see Builder
  33.  * @since 2.8.0
  34.  */
  35. public class RandomAccessFileInputStream extends AbstractInputStream {

  36.     // @formatter:off
  37.     /**
  38.      * Builds a new {@link RandomAccessFileInputStream}.
  39.      *
  40.      * <p>
  41.      * For example:
  42.      * </p>
  43.      * <pre>{@code
  44.      * RandomAccessFileInputStream s = RandomAccessFileInputStream.builder()
  45.      *   .setPath(path)
  46.      *   .setCloseOnClose(true)
  47.      *   .get();}
  48.      * </pre>
  49.      *
  50.      * @see #get()
  51.      * @since 2.12.0
  52.      */
  53.     // @formatter:on
  54.     public static class Builder extends AbstractStreamBuilder<RandomAccessFileInputStream, Builder> {

  55.         private boolean propagateClose;

  56.         /**
  57.          * Constructs a new builder of {@link RandomAccessFileInputStream}.
  58.          */
  59.         public Builder() {
  60.             // empty
  61.         }

  62.         /**
  63.          * Builds a new {@link RandomAccessFileInputStream}.
  64.          * <p>
  65.          * You must set an aspect that supports {@link RandomAccessFile} or {@link File}, otherwise, this method throws an exception. Only set one of
  66.          * RandomAccessFile or an origin that can be converted to a File.
  67.          * </p>
  68.          * <p>
  69.          * This builder uses the following aspects:
  70.          * </p>
  71.          * <ul>
  72.          * <li>{@link RandomAccessFile} gets the target aspect.</li>
  73.          * <li>{@link File}</li>
  74.          * <li>closeOnClose</li>
  75.          * </ul>
  76.          *
  77.          * @return a new instance.
  78.          * @throws IllegalStateException         if the {@code origin} is {@code null}.
  79.          * @throws IllegalStateException         if both RandomAccessFile and origin are set.
  80.          * @throws UnsupportedOperationException if the origin cannot be converted to a {@link RandomAccessFile}.
  81.          * @throws IOException                   if an I/O error occurs converting to an {@link RandomAccessFile} using {@link #getRandomAccessFile()}.
  82.          * @see AbstractOrigin#getFile()
  83.          * @see #getUnchecked()
  84.          */
  85.         @Override
  86.         public RandomAccessFileInputStream get() throws IOException {
  87.             return new RandomAccessFileInputStream(this);
  88.         }

  89.         /**
  90.          * Sets whether to close the underlying file when this stream is closed, defaults to false for compatibility.
  91.          *
  92.          * @param propagateClose Whether to close the underlying file when this stream is closed.
  93.          * @return {@code this} instance.
  94.          */
  95.         public Builder setCloseOnClose(final boolean propagateClose) {
  96.             this.propagateClose = propagateClose;
  97.             return this;
  98.         }

  99.         /**
  100.          * Sets the RandomAccessFile to stream.
  101.          *
  102.          * @param randomAccessFile the RandomAccessFile to stream.
  103.          * @return {@code this} instance.
  104.          */
  105.         @Override // MUST keep this method for binary compatibility since the super version of this method uses a generic which compiles to Object.
  106.         public Builder setRandomAccessFile(final RandomAccessFile randomAccessFile) { // NOPMD see above.
  107.             return super.setRandomAccessFile(randomAccessFile);
  108.         }

  109.     }

  110.     /**
  111.      * Constructs a new {@link Builder}.
  112.      *
  113.      * @return a new {@link Builder}.
  114.      * @since 2.12.0
  115.      */
  116.     public static Builder builder() {
  117.         return new Builder();
  118.     }

  119.     private final boolean propagateClose;
  120.     private final RandomAccessFile randomAccessFile;

  121.     @SuppressWarnings("resource") // caller closes.
  122.     private RandomAccessFileInputStream(final Builder builder) throws IOException {
  123.         this(builder.getRandomAccessFile(), builder.propagateClose);
  124.     }

  125.     /**
  126.      * Constructs a new instance configured to leave the underlying file open when this stream is closed.
  127.      *
  128.      * @param file The file to stream.
  129.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
  130.      */
  131.     @Deprecated
  132.     public RandomAccessFileInputStream(final RandomAccessFile file) {
  133.         this(file, false);
  134.     }

  135.     /**
  136.      * Constructs a new instance.
  137.      *
  138.      * @param file         The file to stream.
  139.      * @param propagateClose Whether to close the underlying file when this stream is closed.
  140.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
  141.      */
  142.     @Deprecated
  143.     public RandomAccessFileInputStream(final RandomAccessFile file, final boolean propagateClose) {
  144.         this.randomAccessFile = Objects.requireNonNull(file, "file");
  145.         this.propagateClose = propagateClose;
  146.     }

  147.     /**
  148.      * Gets an estimate of the number of bytes that can be read (or skipped over) from this input stream.
  149.      * <p>
  150.      * If there are more than {@link Integer#MAX_VALUE} bytes available, return {@link Integer#MAX_VALUE}.
  151.      * </p>
  152.      *
  153.      * @return An estimate of the number of bytes that can be read.
  154.      * @throws IOException If an I/O error occurs.
  155.      */
  156.     @Override
  157.     public int available() throws IOException {
  158.         return Math.toIntExact(Math.min(availableLong(), Integer.MAX_VALUE));
  159.     }

  160.     /**
  161.      * Gets the number of bytes that can be read (or skipped over) from this input stream.
  162.      *
  163.      * @return The number of bytes that can be read.
  164.      * @throws IOException If an I/O error occurs.
  165.      */
  166.     public long availableLong() throws IOException {
  167.         return isClosed() ? 0 : randomAccessFile.length() - randomAccessFile.getFilePointer();
  168.     }

  169.     @Override
  170.     public void close() throws IOException {
  171.         super.close();
  172.         if (propagateClose) {
  173.             randomAccessFile.close();
  174.         }
  175.     }

  176.     /**
  177.      * Copies our bytes from the given starting position for a size to the output stream.
  178.      *
  179.      * @param pos  Where to start copying. The offset position, measured in bytes from the beginning of the file, at which to set the file pointer.
  180.      * @param size The number of bytes to copy.
  181.      * @param os   Where to copy.
  182.      * @return The number of bytes copied..
  183.      * @throws IOException if {@code pos} is less than {@code 0} or if an I/O error occurs.
  184.      * @since 2.19.0
  185.      */
  186.     public long copy(final long pos, final long size, final OutputStream os) throws IOException {
  187.         randomAccessFile.seek(pos);
  188.         return IOUtils.copyLarge(this, os, 0, size);
  189.     }

  190.     /**
  191.      * Gets the underlying file.
  192.      *
  193.      * @return the underlying file.
  194.      */
  195.     public RandomAccessFile getRandomAccessFile() {
  196.         return randomAccessFile;
  197.     }

  198.     /**
  199.      * Tests whether to close the underlying file when this stream is closed, defaults to false for compatibility.
  200.      *
  201.      * @return Whether to close the underlying file when this stream is closed.
  202.      */
  203.     public boolean isCloseOnClose() {
  204.         return propagateClose;
  205.     }

  206.     @Override
  207.     public int read() throws IOException {
  208.         return randomAccessFile.read();
  209.     }

  210.     @Override
  211.     public int read(final byte[] bytes) throws IOException {
  212.         return randomAccessFile.read(bytes);
  213.     }

  214.     @Override
  215.     public int read(final byte[] bytes, final int offset, final int length) throws IOException {
  216.         return randomAccessFile.read(bytes, offset, length);
  217.     }

  218.     @Override
  219.     public long skip(final long skipCount) throws IOException {
  220.         if (skipCount <= 0) {
  221.             return 0;
  222.         }
  223.         final long filePointer = randomAccessFile.getFilePointer();
  224.         final long fileLength = randomAccessFile.length();
  225.         if (filePointer >= fileLength) {
  226.             return 0;
  227.         }
  228.         final long targetPos = filePointer + skipCount;
  229.         final long newPos = targetPos > fileLength ? fileLength - 1 : targetPos;
  230.         if (newPos > 0) {
  231.             randomAccessFile.seek(newPos);
  232.         }
  233.         return randomAccessFile.getFilePointer() - filePointer;
  234.     }
  235. }