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 }