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 }