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.InputStream;
23  import java.io.RandomAccessFile;
24  import java.util.Objects;
25  
26  import org.apache.commons.io.RandomAccessFileMode;
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 InputStream {
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 RandomAccessFile randomAccessFile;
62          private boolean closeOnClose;
63  
64          /**
65           * Builds a new {@link RandomAccessFileInputStream}.
66           * <p>
67           * You must set input that supports {@link RandomAccessFile} or {@link File}, otherwise, this method throws an exception. Only set one of
68           * RandomAccessFile or an origin that can be converted to a File.
69           * </p>
70           * <p>
71           * This builder use the following aspects:
72           * </p>
73           * <ul>
74           * <li>{@link RandomAccessFile}</li>
75           * <li>{@link File}</li>
76           * <li>closeOnClose</li>
77           * </ul>
78           *
79           * @return a new instance.
80           * @throws IllegalStateException         if the {@code origin} is {@code null}.
81           * @throws IllegalStateException         if both RandomAccessFile and origin are set.
82           * @throws UnsupportedOperationException if the origin cannot be converted to a {@link File}.
83           * @see AbstractOrigin#getFile()
84           */
85          @SuppressWarnings("resource") // Caller closes depending on settings
86          @Override
87          public RandomAccessFileInputStream get() throws IOException {
88              if (randomAccessFile != null) {
89                  if (getOrigin() != null) {
90                      throw new IllegalStateException(String.format("Only set one of RandomAccessFile (%s) or origin (%s)", randomAccessFile, getOrigin()));
91                  }
92                  return new RandomAccessFileInputStream(randomAccessFile, closeOnClose);
93              }
94              return new RandomAccessFileInputStream(RandomAccessFileMode.READ_ONLY.create(checkOrigin().getFile()), closeOnClose);
95          }
96  
97          /**
98           * Sets whether to close the underlying file when this stream is closed.
99           *
100          * @param closeOnClose Whether to close the underlying file when this stream is closed.
101          * @return this
102          */
103         public Builder setCloseOnClose(final boolean closeOnClose) {
104             this.closeOnClose = closeOnClose;
105             return this;
106         }
107 
108         /**
109          * Sets the RandomAccessFile to stream.
110          *
111          * @param randomAccessFile the RandomAccessFile to stream.
112          * @return this
113          */
114         public Builder setRandomAccessFile(final RandomAccessFile randomAccessFile) {
115             this.randomAccessFile = randomAccessFile;
116             return this;
117         }
118 
119     }
120 
121     /**
122      * Constructs a new {@link Builder}.
123      *
124      * @return a new {@link Builder}.
125      * @since 2.12.0
126      */
127     public static Builder builder() {
128         return new Builder();
129     }
130 
131     private final boolean closeOnClose;
132     private final RandomAccessFile randomAccessFile;
133 
134     /**
135      * Constructs a new instance configured to leave the underlying file open when this stream is closed.
136      *
137      * @param file The file to stream.
138      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
139      */
140     @Deprecated
141     public RandomAccessFileInputStream(final RandomAccessFile file) {
142         this(file, false);
143     }
144 
145     /**
146      * Constructs a new instance.
147      *
148      * @param file         The file to stream.
149      * @param closeOnClose Whether to close the underlying file when this stream is closed.
150      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
151      */
152     @Deprecated
153     public RandomAccessFileInputStream(final RandomAccessFile file, final boolean closeOnClose) {
154         this.randomAccessFile = Objects.requireNonNull(file, "file");
155         this.closeOnClose = closeOnClose;
156     }
157 
158     /**
159      * Returns an estimate of the number of bytes that can be read (or skipped over) from this input stream.
160      *
161      * If there are more than {@link Integer#MAX_VALUE} bytes available, return {@link Integer#MAX_VALUE}.
162      *
163      * @return An estimate of the number of bytes that can be read.
164      * @throws IOException If an I/O error occurs.
165      */
166     @Override
167     public int available() throws IOException {
168         final long avail = availableLong();
169         if (avail > Integer.MAX_VALUE) {
170             return Integer.MAX_VALUE;
171         }
172         return (int) avail;
173     }
174 
175     /**
176      * Returns the number of bytes that can be read (or skipped over) from this input stream.
177      *
178      * @return The number of bytes that can be read.
179      * @throws IOException If an I/O error occurs.
180      */
181     public long availableLong() throws IOException {
182         return randomAccessFile.length() - randomAccessFile.getFilePointer();
183     }
184 
185     @Override
186     public void close() throws IOException {
187         super.close();
188         if (closeOnClose) {
189             randomAccessFile.close();
190         }
191     }
192 
193     /**
194      * Gets the underlying file.
195      *
196      * @return the underlying file.
197      */
198     public RandomAccessFile getRandomAccessFile() {
199         return randomAccessFile;
200     }
201 
202     /**
203      * Returns whether to close the underlying file when this stream is closed.
204      *
205      * @return Whether to close the underlying file when this stream is closed.
206      */
207     public boolean isCloseOnClose() {
208         return closeOnClose;
209     }
210 
211     @Override
212     public int read() throws IOException {
213         return randomAccessFile.read();
214     }
215 
216     @Override
217     public int read(final byte[] bytes) throws IOException {
218         return randomAccessFile.read(bytes);
219     }
220 
221     @Override
222     public int read(final byte[] bytes, final int offset, final int length) throws IOException {
223         return randomAccessFile.read(bytes, offset, length);
224     }
225 
226     @Override
227     public long skip(final long skipCount) throws IOException {
228         if (skipCount <= 0) {
229             return 0;
230         }
231         final long filePointer = randomAccessFile.getFilePointer();
232         final long fileLength = randomAccessFile.length();
233         if (filePointer >= fileLength) {
234             return 0;
235         }
236         final long targetPos = filePointer + skipCount;
237         final long newPos = targetPos > fileLength ? fileLength - 1 : targetPos;
238         if (newPos > 0) {
239             randomAccessFile.seek(newPos);
240         }
241         return randomAccessFile.getFilePointer() - filePointer;
242     }
243 }