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.  *      http://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(getRandomAccessFile(), propagateClose);
  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.     /**
  122.      * Constructs a new instance configured to leave the underlying file open when this stream is closed.
  123.      *
  124.      * @param file The file to stream.
  125.      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
  126.      */
  127.     @Deprecated
  128.     public RandomAccessFileInputStream(final RandomAccessFile file) {
  129.         this(file, false);
  130.     }

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

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

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

  165.     @Override
  166.     public void close() throws IOException {
  167.         super.close();
  168.         if (propagateClose) {
  169.             randomAccessFile.close();
  170.         }
  171.     }

  172.     /**
  173.      * Copies our bytes from the given starting position for a size to the output stream.
  174.      *
  175.      * @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.
  176.      * @param size The number of bytes to copy.
  177.      * @param os   Where to copy.
  178.      * @return The number of bytes copied..
  179.      * @throws IOException if {@code pos} is less than {@code 0} or if an I/O error occurs.
  180.      * @since 2.19.0
  181.      */
  182.     public long copy(final long pos, final long size, final OutputStream os) throws IOException {
  183.         randomAccessFile.seek(pos);
  184.         return IOUtils.copyLarge(this, os, 0, size);
  185.     }

  186.     /**
  187.      * Gets the underlying file.
  188.      *
  189.      * @return the underlying file.
  190.      */
  191.     public RandomAccessFile getRandomAccessFile() {
  192.         return randomAccessFile;
  193.     }

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

  202.     @Override
  203.     public int read() throws IOException {
  204.         return randomAccessFile.read();
  205.     }

  206.     @Override
  207.     public int read(final byte[] bytes) throws IOException {
  208.         return randomAccessFile.read(bytes);
  209.     }

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

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