1 /*
2 * Licensed under the Apache License, Version 2.0 (the "License");
3 * you may not use this file except in compliance with the License.
4 * You may obtain a copy of the License at
5 *
6 * https://www.apache.org/licenses/LICENSE-2.0
7 *
8 * Unless required by applicable law or agreed to in writing, software
9 * distributed under the License is distributed on an "AS IS" BASIS,
10 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 * See the License for the specific language governing permissions and
12 * limitations under the License.
13 */
14 package org.apache.commons.io.input;
15
16 import static org.apache.commons.io.IOUtils.EOF;
17
18 import java.io.BufferedInputStream;
19 import java.io.File;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.nio.ByteBuffer;
23 import java.nio.channels.FileChannel;
24 import java.nio.file.Path;
25 import java.nio.file.StandardOpenOption;
26
27 import org.apache.commons.io.IOUtils;
28 import org.apache.commons.io.build.AbstractStreamBuilder;
29
30 /**
31 * {@link InputStream} implementation which uses direct buffer to read a file to avoid extra copy of data between Java and native memory which happens when
32 * using {@link BufferedInputStream}. Unfortunately, this is not something already available in JDK, {@code sun.nio.ch.ChannelInputStream} supports
33 * reading a file using NIO, but does not support buffering.
34 * <p>
35 * To build an instance, use {@link Builder}.
36 * </p>
37 * <p>
38 * This class was ported and adapted from Apache Spark commit 933dc6cb7b3de1d8ccaf73d124d6eb95b947ed19 where it was called {@code NioBufferedFileInputStream}.
39 * </p>
40 *
41 * @see Builder
42 * @since 2.9.0
43 */
44 public final class BufferedFileChannelInputStream extends InputStream {
45
46 // @formatter:off
47 /**
48 * Builds a new {@link BufferedFileChannelInputStream}.
49 *
50 * <p>
51 * Using File IO:
52 * </p>
53 * <pre>{@code
54 * BufferedFileChannelInputStream s = BufferedFileChannelInputStream.builder()
55 * .setFile(file)
56 * .setBufferSize(4096)
57 * .get();}
58 * </pre>
59 * <p>
60 * Using NIO Path:
61 * </p>
62 * <pre>{@code
63 * BufferedFileChannelInputStream s = BufferedFileChannelInputStream.builder()
64 * .setPath(path)
65 * .setBufferSize(4096)
66 * .get();}
67 * </pre>
68 *
69 * @see #get()
70 * @since 2.12.0
71 */
72 // @formatter:on
73 public static class Builder extends AbstractStreamBuilder<BufferedFileChannelInputStream, Builder> {
74
75 private FileChannel fileChannel;
76
77 /**
78 * Constructs a new builder of {@link BufferedFileChannelInputStream}.
79 */
80 public Builder() {
81 // empty
82 }
83
84 /**
85 * Builds a new {@link BufferedFileChannelInputStream}.
86 * <p>
87 * You must set an aspect that supports {@link #getInputStream()}, otherwise, this method throws an exception.
88 * </p>
89 * <p>
90 * This builder uses the following aspects:
91 * </p>
92 * <ul>
93 * <li>{@link FileChannel} takes precedence is set. </li>
94 * <li>{@link #getPath()} if the file channel is not set.</li>
95 * <li>{@link #getBufferSize()}</li>
96 * </ul>
97 *
98 * @return a new instance.
99 * @throws IllegalStateException if the {@code origin} is {@code null}.
100 * @throws UnsupportedOperationException if the origin cannot be converted to a {@link Path}.
101 * @throws IOException if an I/O error occurs converting to an {@link Path} using {@link #getPath()}.
102 * @see #getPath()
103 * @see #getBufferSize()
104 * @see #getUnchecked()
105 */
106 @Override
107 public BufferedFileChannelInputStream get() throws IOException {
108 return new BufferedFileChannelInputStream(this);
109 }
110
111 /**
112 * Sets the file channel.
113 * <p>
114 * This setting takes precedence over all others.
115 * </p>
116 *
117 * @param fileChannel the file channel.
118 * @return this instance.
119 * @since 2.18.0
120 */
121 public Builder setFileChannel(final FileChannel fileChannel) {
122 this.fileChannel = fileChannel;
123 return this;
124 }
125
126 }
127
128 /**
129 * Constructs a new {@link Builder}.
130 *
131 * @return a new {@link Builder}.
132 * @since 2.12.0
133 */
134 public static Builder builder() {
135 return new Builder();
136 }
137
138 private final ByteBuffer byteBuffer;
139
140 private final FileChannel fileChannel;
141
142 @SuppressWarnings("resource")
143 private BufferedFileChannelInputStream(final Builder builder) throws IOException {
144 this.fileChannel = builder.fileChannel != null ? builder.fileChannel : FileChannel.open(builder.getPath(), StandardOpenOption.READ);
145 this.byteBuffer = ByteBuffer.allocateDirect(builder.getBufferSize());
146 this.byteBuffer.flip();
147 }
148
149 /**
150 * Constructs a new instance for the given File.
151 *
152 * @param file The file to stream.
153 * @throws IOException If an I/O error occurs
154 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
155 */
156 @Deprecated
157 public BufferedFileChannelInputStream(final File file) throws IOException {
158 this(file, IOUtils.DEFAULT_BUFFER_SIZE);
159 }
160
161 /**
162 * Constructs a new instance for the given File and buffer size.
163 *
164 * @param file The file to stream.
165 * @param bufferSize buffer size.
166 * @throws IOException If an I/O error occurs
167 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
168 */
169 @Deprecated
170 public BufferedFileChannelInputStream(final File file, final int bufferSize) throws IOException {
171 this(file.toPath(), bufferSize);
172 }
173
174 /**
175 * Constructs a new instance for the given Path.
176 *
177 * @param path The path to stream.
178 * @throws IOException If an I/O error occurs
179 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
180 */
181 @Deprecated
182 public BufferedFileChannelInputStream(final Path path) throws IOException {
183 this(path, IOUtils.DEFAULT_BUFFER_SIZE);
184 }
185
186 /**
187 * Constructs a new instance for the given Path and buffer size.
188 *
189 * @param path The path to stream.
190 * @param bufferSize buffer size.
191 * @throws IOException If an I/O error occurs
192 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
193 */
194 @Deprecated
195 public BufferedFileChannelInputStream(final Path path, final int bufferSize) throws IOException {
196 this(builder().setPath(path).setBufferSize(bufferSize));
197 }
198
199 @Override
200 public synchronized int available() throws IOException {
201 if (!fileChannel.isOpen()) {
202 return 0;
203 }
204 if (!refill()) {
205 return 0;
206 }
207 return byteBuffer.remaining();
208 }
209
210 /**
211 * Attempts to clean up a ByteBuffer if it is direct or memory-mapped. This uses an *unsafe* Sun API that will cause errors if one attempts to read from the
212 * disposed buffer. However, neither the bytes allocated to direct buffers nor file descriptors opened for memory-mapped buffers put pressure on the garbage
213 * collector. Waiting for garbage collection may lead to the depletion of off-heap memory or huge numbers of open files. There's unfortunately no standard
214 * API to manually dispose of these kinds of buffers.
215 *
216 * @param buffer the buffer to clean.
217 */
218 private void clean(final ByteBuffer buffer) {
219 if (buffer.isDirect()) {
220 cleanDirectBuffer(buffer);
221 }
222 }
223
224 /**
225 * In Java 8, the type of {@code sun.nio.ch.DirectBuffer.cleaner()} was {@code sun.misc.Cleaner}, and it was possible to access the method
226 * {@code sun.misc.Cleaner.clean()} to invoke it. The type changed to {@code jdk.internal.ref.Cleaner} in later JDKs, and the {@code clean()} method is not
227 * accessible even with reflection. However {@code sun.misc.Unsafe} added an {@code invokeCleaner()} method in JDK 9+ and this is still accessible with
228 * reflection.
229 *
230 * @param buffer the buffer to clean. must be a DirectBuffer.
231 */
232 private void cleanDirectBuffer(final ByteBuffer buffer) {
233 if (ByteBufferCleaner.isSupported()) {
234 ByteBufferCleaner.clean(buffer);
235 }
236 }
237
238 @Override
239 public synchronized void close() throws IOException {
240 try {
241 fileChannel.close();
242 } finally {
243 clean(byteBuffer);
244 }
245 }
246
247 @Override
248 public synchronized int read() throws IOException {
249 if (!refill()) {
250 return EOF;
251 }
252 return byteBuffer.get() & 0xFF;
253 }
254
255 @Override
256 public synchronized int read(final byte[] b, final int offset, int len) throws IOException {
257 if (offset < 0 || len < 0 || offset + len < 0 || offset + len > b.length) {
258 throw new IndexOutOfBoundsException();
259 }
260 if (!refill()) {
261 return EOF;
262 }
263 len = Math.min(len, byteBuffer.remaining());
264 byteBuffer.get(b, offset, len);
265 return len;
266 }
267
268 /**
269 * Checks whether data is left to be read from the input stream.
270 *
271 * @return true if data is left, false otherwise
272 * @throws IOException if an I/O error occurs.
273 */
274 private boolean refill() throws IOException {
275 Input.checkOpen(fileChannel.isOpen());
276 if (!byteBuffer.hasRemaining()) {
277 byteBuffer.clear();
278 int nRead = 0;
279 while (nRead == 0) {
280 nRead = fileChannel.read(byteBuffer);
281 }
282 byteBuffer.flip();
283 return nRead >= 0;
284 }
285 return true;
286 }
287
288 @Override
289 public synchronized long skip(final long n) throws IOException {
290 if (n <= 0L) {
291 return 0L;
292 }
293 if (byteBuffer.remaining() >= n) {
294 // The buffered content is enough to skip
295 byteBuffer.position(byteBuffer.position() + (int) n);
296 return n;
297 }
298 final long skippedFromBuffer = byteBuffer.remaining();
299 final long toSkipFromFileChannel = n - skippedFromBuffer;
300 // Discard everything we have read in the buffer.
301 byteBuffer.position(0);
302 byteBuffer.flip();
303 return skippedFromBuffer + skipFromFileChannel(toSkipFromFileChannel);
304 }
305
306 private long skipFromFileChannel(final long n) throws IOException {
307 final long currentFilePosition = fileChannel.position();
308 final long size = fileChannel.size();
309 if (n > size - currentFilePosition) {
310 fileChannel.position(size);
311 return size - currentFilePosition;
312 }
313 fileChannel.position(currentFilePosition + n);
314 return n;
315 }
316
317 }