View Javadoc
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  
18  package org.apache.commons.io.input;
19  
20  import java.io.File;
21  import java.io.IOException;
22  import java.io.OutputStream;
23  import java.io.RandomAccessFile;
24  import java.util.Objects;
25  
26  import org.apache.commons.io.IOUtils;
27  import org.apache.commons.io.build.AbstractOrigin;
28  import org.apache.commons.io.build.AbstractStreamBuilder;
29  
30  /**
31   * Streams data from a {@link RandomAccessFile} starting at its current position.
32   * <p>
33   * To build an instance, use {@link Builder}.
34   * </p>
35   *
36   * @see Builder
37   * @since 2.8.0
38   */
39  public class RandomAccessFileInputStream extends AbstractInputStream {
40  
41      // @formatter:off
42      /**
43       * Builds a new {@link RandomAccessFileInputStream}.
44       *
45       * <p>
46       * For example:
47       * </p>
48       * <pre>{@code
49       * RandomAccessFileInputStream s = RandomAccessFileInputStream.builder()
50       *   .setPath(path)
51       *   .setCloseOnClose(true)
52       *   .get();}
53       * </pre>
54       *
55       * @see #get()
56       * @since 2.12.0
57       */
58      // @formatter:on
59      public static class Builder extends AbstractStreamBuilder<RandomAccessFileInputStream, Builder> {
60  
61          private boolean propagateClose;
62  
63          /**
64           * Constructs a new builder of {@link RandomAccessFileInputStream}.
65           */
66          public Builder() {
67              // empty
68          }
69  
70          /**
71           * Builds a new {@link RandomAccessFileInputStream}.
72           * <p>
73           * You must set an aspect that supports {@link RandomAccessFile} or {@link File}, otherwise, this method throws an exception. Only set one of
74           * RandomAccessFile or an origin that can be converted to a File.
75           * </p>
76           * <p>
77           * This builder uses the following aspects:
78           * </p>
79           * <ul>
80           * <li>{@link RandomAccessFile} gets the target aspect.</li>
81           * <li>{@link File}</li>
82           * <li>closeOnClose</li>
83           * </ul>
84           *
85           * @return a new instance.
86           * @throws IllegalStateException         if the {@code origin} is {@code null}.
87           * @throws IllegalStateException         if both RandomAccessFile and origin are set.
88           * @throws UnsupportedOperationException if the origin cannot be converted to a {@link RandomAccessFile}.
89           * @throws IOException                   if an I/O error occurs converting to an {@link RandomAccessFile} using {@link #getRandomAccessFile()}.
90           * @see AbstractOrigin#getFile()
91           * @see #getUnchecked()
92           */
93          @Override
94          public RandomAccessFileInputStream get() throws IOException {
95              return new RandomAccessFileInputStream(getRandomAccessFile(), propagateClose);
96          }
97  
98          /**
99           * Sets whether to close the underlying file when this stream is closed, defaults to false for compatibility.
100          *
101          * @param propagateClose Whether to close the underlying file when this stream is closed.
102          * @return {@code this} instance.
103          */
104         public Builder setCloseOnClose(final boolean propagateClose) {
105             this.propagateClose = propagateClose;
106             return this;
107         }
108 
109         /**
110          * Sets the RandomAccessFile to stream.
111          *
112          * @param randomAccessFile the RandomAccessFile to stream.
113          * @return {@code this} instance.
114          */
115         @Override // MUST keep this method for binary compatibility since the super version of this method uses a generic which compiles to Object.
116         public Builder setRandomAccessFile(final RandomAccessFile randomAccessFile) { // NOPMD see above.
117             return super.setRandomAccessFile(randomAccessFile);
118         }
119 
120     }
121 
122     /**
123      * Constructs a new {@link Builder}.
124      *
125      * @return a new {@link Builder}.
126      * @since 2.12.0
127      */
128     public static Builder builder() {
129         return new Builder();
130     }
131 
132     private final boolean propagateClose;
133     private final RandomAccessFile randomAccessFile;
134 
135     /**
136      * Constructs a new instance configured to leave the underlying file open when this stream is closed.
137      *
138      * @param file The file to stream.
139      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
140      */
141     @Deprecated
142     public RandomAccessFileInputStream(final RandomAccessFile file) {
143         this(file, false);
144     }
145 
146     /**
147      * Constructs a new instance.
148      *
149      * @param file         The file to stream.
150      * @param propagateClose Whether to close the underlying file when this stream is closed.
151      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
152      */
153     @Deprecated
154     public RandomAccessFileInputStream(final RandomAccessFile file, final boolean propagateClose) {
155         this.randomAccessFile = Objects.requireNonNull(file, "file");
156         this.propagateClose = propagateClose;
157     }
158 
159     /**
160      * Gets an estimate of the number of bytes that can be read (or skipped over) from this input stream.
161      * <p>
162      * If there are more than {@link Integer#MAX_VALUE} bytes available, return {@link Integer#MAX_VALUE}.
163      * </p>
164      *
165      * @return An estimate of the number of bytes that can be read.
166      * @throws IOException If an I/O error occurs.
167      */
168     @Override
169     public int available() throws IOException {
170         return Math.toIntExact(Math.min(availableLong(), Integer.MAX_VALUE));
171     }
172 
173     /**
174      * Gets the number of bytes that can be read (or skipped over) from this input stream.
175      *
176      * @return The number of bytes that can be read.
177      * @throws IOException If an I/O error occurs.
178      */
179     public long availableLong() throws IOException {
180         return isClosed() ? 0 : randomAccessFile.length() - randomAccessFile.getFilePointer();
181     }
182 
183     @Override
184     public void close() throws IOException {
185         super.close();
186         if (propagateClose) {
187             randomAccessFile.close();
188         }
189     }
190 
191     /**
192      * Copies our bytes from the given starting position for a size to the output stream.
193      *
194      * @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.
195      * @param size The number of bytes to copy.
196      * @param os   Where to copy.
197      * @return The number of bytes copied..
198      * @throws IOException if {@code pos} is less than {@code 0} or if an I/O error occurs.
199      * @since 2.19.0
200      */
201     public long copy(final long pos, final long size, final OutputStream os) throws IOException {
202         randomAccessFile.seek(pos);
203         return IOUtils.copyLarge(this, os, 0, size);
204     }
205 
206     /**
207      * Gets the underlying file.
208      *
209      * @return the underlying file.
210      */
211     public RandomAccessFile getRandomAccessFile() {
212         return randomAccessFile;
213     }
214 
215     /**
216      * Tests whether to close the underlying file when this stream is closed, defaults to false for compatibility.
217      *
218      * @return Whether to close the underlying file when this stream is closed.
219      */
220     public boolean isCloseOnClose() {
221         return propagateClose;
222     }
223 
224     @Override
225     public int read() throws IOException {
226         return randomAccessFile.read();
227     }
228 
229     @Override
230     public int read(final byte[] bytes) throws IOException {
231         return randomAccessFile.read(bytes);
232     }
233 
234     @Override
235     public int read(final byte[] bytes, final int offset, final int length) throws IOException {
236         return randomAccessFile.read(bytes, offset, length);
237     }
238 
239     @Override
240     public long skip(final long skipCount) throws IOException {
241         if (skipCount <= 0) {
242             return 0;
243         }
244         final long filePointer = randomAccessFile.getFilePointer();
245         final long fileLength = randomAccessFile.length();
246         if (filePointer >= fileLength) {
247             return 0;
248         }
249         final long targetPos = filePointer + skipCount;
250         final long newPos = targetPos > fileLength ? fileLength - 1 : targetPos;
251         if (newPos > 0) {
252             randomAccessFile.seek(newPos);
253         }
254         return randomAccessFile.getFilePointer() - filePointer;
255     }
256 }