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    *      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  
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(this);
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     @SuppressWarnings("resource") // caller closes.
136     private RandomAccessFileInputStream(final Builder builder) throws IOException {
137         this(builder.getRandomAccessFile(), builder.propagateClose);
138     }
139 
140     /**
141      * Constructs a new instance configured to leave the underlying file open when this stream is closed.
142      *
143      * @param file The file to stream.
144      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
145      */
146     @Deprecated
147     public RandomAccessFileInputStream(final RandomAccessFile file) {
148         this(file, false);
149     }
150 
151     /**
152      * Constructs a new instance.
153      *
154      * @param file         The file to stream.
155      * @param propagateClose Whether to close the underlying file when this stream is closed.
156      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
157      */
158     @Deprecated
159     public RandomAccessFileInputStream(final RandomAccessFile file, final boolean propagateClose) {
160         this.randomAccessFile = Objects.requireNonNull(file, "file");
161         this.propagateClose = propagateClose;
162     }
163 
164     /**
165      * Gets an estimate of the number of bytes that can be read (or skipped over) from this input stream.
166      * <p>
167      * If there are more than {@link Integer#MAX_VALUE} bytes available, return {@link Integer#MAX_VALUE}.
168      * </p>
169      *
170      * @return An estimate of the number of bytes that can be read.
171      * @throws IOException If an I/O error occurs.
172      */
173     @Override
174     public int available() throws IOException {
175         return Math.toIntExact(Math.min(availableLong(), Integer.MAX_VALUE));
176     }
177 
178     /**
179      * Gets the number of bytes that can be read (or skipped over) from this input stream.
180      *
181      * @return The number of bytes that can be read.
182      * @throws IOException If an I/O error occurs.
183      */
184     public long availableLong() throws IOException {
185         return isClosed() ? 0 : randomAccessFile.length() - randomAccessFile.getFilePointer();
186     }
187 
188     @Override
189     public void close() throws IOException {
190         super.close();
191         if (propagateClose) {
192             randomAccessFile.close();
193         }
194     }
195 
196     /**
197      * Copies our bytes from the given starting position for a size to the output stream.
198      *
199      * @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.
200      * @param size The number of bytes to copy.
201      * @param os   Where to copy.
202      * @return The number of bytes copied..
203      * @throws IOException if {@code pos} is less than {@code 0} or if an I/O error occurs.
204      * @since 2.19.0
205      */
206     public long copy(final long pos, final long size, final OutputStream os) throws IOException {
207         randomAccessFile.seek(pos);
208         return IOUtils.copyLarge(this, os, 0, size);
209     }
210 
211     /**
212      * Gets the underlying file.
213      *
214      * @return the underlying file.
215      */
216     public RandomAccessFile getRandomAccessFile() {
217         return randomAccessFile;
218     }
219 
220     /**
221      * Tests whether to close the underlying file when this stream is closed, defaults to false for compatibility.
222      *
223      * @return Whether to close the underlying file when this stream is closed.
224      */
225     public boolean isCloseOnClose() {
226         return propagateClose;
227     }
228 
229     @Override
230     public int read() throws IOException {
231         return randomAccessFile.read();
232     }
233 
234     @Override
235     public int read(final byte[] bytes) throws IOException {
236         return randomAccessFile.read(bytes);
237     }
238 
239     @Override
240     public int read(final byte[] bytes, final int offset, final int length) throws IOException {
241         return randomAccessFile.read(bytes, offset, length);
242     }
243 
244     @Override
245     public long skip(final long skipCount) throws IOException {
246         if (skipCount <= 0) {
247             return 0;
248         }
249         final long filePointer = randomAccessFile.getFilePointer();
250         final long fileLength = randomAccessFile.length();
251         if (filePointer >= fileLength) {
252             return 0;
253         }
254         final long targetPos = filePointer + skipCount;
255         final long newPos = targetPos > fileLength ? fileLength - 1 : targetPos;
256         if (newPos > 0) {
257             randomAccessFile.seek(newPos);
258         }
259         return randomAccessFile.getFilePointer() - filePointer;
260     }
261 }