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.IOException;
21  import java.io.InputStream;
22  import java.io.RandomAccessFile;
23  import java.util.Objects;
24  
25  import org.apache.commons.io.RandomAccessFileMode;
26  import org.apache.commons.io.build.AbstractOrigin;
27  import org.apache.commons.io.build.AbstractStreamBuilder;
28  
29  /**
30   * Streams data from a {@link RandomAccessFile} starting at its current position.
31   * <p>
32   * To build an instance, see {@link Builder}.
33   * </p>
34   * @since 2.8.0
35   */
36  public class RandomAccessFileInputStream extends InputStream {
37  
38      /**
39       * Builds a new {@link RandomAccessFileInputStream} instance.
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       * @since 2.12.0
51       */
52      public static class Builder extends AbstractStreamBuilder<RandomAccessFileInputStream, Builder> {
53  
54          private RandomAccessFile randomAccessFile;
55          private boolean closeOnClose;
56  
57          /**
58           * Constructs a new instance.
59           * <p>
60           * This builder use the aspects RandomAccessFile or File, and closeOnClose. Only set one of RandomAccessFile or an origin that can be converted to a
61           * File.
62           * </p>
63           * <p>
64           * If RandomAccessFile is not set, then you must provide an origin that can be converted to a File by this builder, otherwise, this call will throw an
65           * {@link UnsupportedOperationException}.
66           * </p>
67           *
68           * @return a new instance.
69           * @throws IllegalStateException if both RandomAccessFile and origin are set.
70           * @throws UnsupportedOperationException if the origin cannot provide a File.
71           * @see AbstractOrigin#getFile()
72           */
73          @SuppressWarnings("resource") // Caller closes depending on settings
74          @Override
75          public RandomAccessFileInputStream get() throws IOException {
76              if (randomAccessFile != null) {
77                  if (getOrigin() != null) {
78                      throw new IllegalStateException(String.format("Only set one of RandomAccessFile (%s) or origin (%s)", randomAccessFile, getOrigin()));
79                  }
80                  return new RandomAccessFileInputStream(randomAccessFile, closeOnClose);
81              }
82              return new RandomAccessFileInputStream(RandomAccessFileMode.READ_ONLY.create(getOrigin().getFile()), closeOnClose);
83          }
84  
85          /**
86           * Sets whether to close the underlying file when this stream is closed.
87           *
88           * @param closeOnClose Whether to close the underlying file when this stream is closed.
89           * @return this
90           */
91          public Builder setCloseOnClose(final boolean closeOnClose) {
92              this.closeOnClose = closeOnClose;
93              return this;
94          }
95  
96          /**
97           * Sets the RandomAccessFile to stream.
98           *
99           * @param randomAccessFile the RandomAccessFile to stream.
100          * @return this
101          */
102         public Builder setRandomAccessFile(final RandomAccessFile randomAccessFile) {
103             this.randomAccessFile = randomAccessFile;
104             return this;
105         }
106 
107     }
108 
109     /**
110      * Constructs a new {@link Builder}.
111      *
112      * @return a new {@link Builder}.
113      * @since 2.12.0
114      */
115     public static Builder builder() {
116         return new Builder();
117     }
118 
119     private final boolean closeOnClose;
120     private final RandomAccessFile randomAccessFile;
121 
122     /**
123      * Constructs a new instance configured to leave the underlying file open when this stream is closed.
124      *
125      * @param file The file to stream.
126      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
127      */
128     @Deprecated
129     public RandomAccessFileInputStream(final RandomAccessFile file) {
130         this(file, false);
131     }
132 
133     /**
134      * Constructs a new instance.
135      *
136      * @param file         The file to stream.
137      * @param closeOnClose Whether to close the underlying file when this stream is closed.
138      * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
139      */
140     @Deprecated
141     public RandomAccessFileInputStream(final RandomAccessFile file, final boolean closeOnClose) {
142         this.randomAccessFile = Objects.requireNonNull(file, "file");
143         this.closeOnClose = closeOnClose;
144     }
145 
146     /**
147      * Returns an estimate of the number of bytes that can be read (or skipped over) from this input stream.
148      *
149      * If there are more than {@link Integer#MAX_VALUE} bytes available, return {@link Integer#MAX_VALUE}.
150      *
151      * @return An estimate of the number of bytes that can be read.
152      * @throws IOException If an I/O error occurs.
153      */
154     @Override
155     public int available() throws IOException {
156         final long avail = availableLong();
157         if (avail > Integer.MAX_VALUE) {
158             return Integer.MAX_VALUE;
159         }
160         return (int) avail;
161     }
162 
163     /**
164      * Returns the number of bytes that can be read (or skipped over) from this input stream.
165      *
166      * @return The number of bytes that can be read.
167      * @throws IOException If an I/O error occurs.
168      */
169     public long availableLong() throws IOException {
170         return randomAccessFile.length() - randomAccessFile.getFilePointer();
171     }
172 
173     @Override
174     public void close() throws IOException {
175         super.close();
176         if (closeOnClose) {
177             randomAccessFile.close();
178         }
179     }
180 
181     /**
182      * Gets the underlying file.
183      *
184      * @return the underlying file.
185      */
186     public RandomAccessFile getRandomAccessFile() {
187         return randomAccessFile;
188     }
189 
190     /**
191      * Returns whether to close the underlying file when this stream is closed.
192      *
193      * @return Whether to close the underlying file when this stream is closed.
194      */
195     public boolean isCloseOnClose() {
196         return closeOnClose;
197     }
198 
199     @Override
200     public int read() throws IOException {
201         return randomAccessFile.read();
202     }
203 
204     @Override
205     public int read(final byte[] bytes) throws IOException {
206         return randomAccessFile.read(bytes);
207     }
208 
209     @Override
210     public int read(final byte[] bytes, final int offset, final int length) throws IOException {
211         return randomAccessFile.read(bytes, offset, length);
212     }
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 }