| /* | |
| * Copyright (c) 1994, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package java.io; | |
| import java.nio.channels.FileChannel; | |
| import java.util.Arrays; | |
| import jdk.internal.util.ArraysSupport; | |
| import jdk.internal.event.FileReadEvent; | |
| import jdk.internal.vm.annotation.Stable; | |
| import sun.nio.ch.FileChannelImpl; | |
| /** | |
| * A {@code FileInputStream} obtains input bytes | |
| * from a file in a file system. What files | |
| * are available depends on the host environment. | |
| * | |
| * <p>{@code FileInputStream} is meant for reading streams of raw bytes | |
| * such as image data. For reading streams of characters, consider using | |
| * {@code FileReader}. | |
| * | |
| * @apiNote | |
| * The {@link #close} method should be called to release resources used by this | |
| * stream, either directly, or with the {@code try}-with-resources statement. | |
| * | |
| * @implSpec | |
| * Subclasses are responsible for the cleanup of resources acquired by the subclass. | |
| * Subclasses requiring that resource cleanup take place after a stream becomes | |
| * unreachable should use {@link java.lang.ref.Cleaner} or some other mechanism. | |
| * | |
| * @author Arthur van Hoff | |
| * @see java.io.File | |
| * @see java.io.FileDescriptor | |
| * @see java.io.FileOutputStream | |
| * @see java.nio.file.Files#newInputStream | |
| * @since 1.0 | |
| */ | |
| public class FileInputStream extends InputStream | |
| { | |
| private static final int DEFAULT_BUFFER_SIZE = 8192; | |
| /** | |
| * Flag set by jdk.internal.event.JFRTracing to indicate if | |
| * file reads should be traced by JFR. | |
| */ | |
| private static boolean jfrTracing; | |
| /* File Descriptor - handle to the open file */ | |
| private final FileDescriptor fd; | |
| /** | |
| * The path of the referenced file | |
| * (null if the stream is created with a file descriptor) | |
| */ | |
| private final String path; | |
| private volatile FileChannel channel; | |
| private final Object closeLock = new Object(); | |
| private volatile boolean closed; | |
| // This field indicates whether the file is a regular file as some | |
| // operations need the current position which requires seeking | |
| private @Stable Boolean isRegularFile; | |
| /** | |
| * Creates a {@code FileInputStream} to read from an existing file | |
| * named by the path name {@code name}. | |
| * {@linkplain java.nio.file##links Symbolic links} | |
| * are automatically redirected to the <i>target</i> of the link. | |
| * A new {@code FileDescriptor} | |
| * object is created to represent this file | |
| * connection. | |
| * <p> | |
| * If the named file does not exist, is a directory rather than a regular | |
| * file, or for some other reason cannot be opened for reading then a | |
| * {@code FileNotFoundException} is thrown. | |
| * | |
| * @param name the system-dependent file name. | |
| * @throws FileNotFoundException if the file does not exist, | |
| * is a directory rather than a regular file, | |
| * or for some other reason cannot be opened for | |
| * reading. | |
| */ | |
| public FileInputStream(String name) throws FileNotFoundException { | |
| this(name != null ? new File(name) : null); | |
| } | |
| /** | |
| * Creates a {@code FileInputStream} to read from an existing file | |
| * represented by the {@code File} object {@code file}. | |
| * {@linkplain java.nio.file##links Symbolic links} | |
| * are automatically redirected to the <i>target</i> of the link. | |
| * A new {@code FileDescriptor} object | |
| * is created to represent this file connection. | |
| * <p> | |
| * If the named file does not exist, is a directory rather than a regular | |
| * file, or for some other reason cannot be opened for reading then a | |
| * {@code FileNotFoundException} is thrown. | |
| * | |
| * @param file the file to be opened for reading. | |
| * @throws FileNotFoundException if the file does not exist, | |
| * is a directory rather than a regular file, | |
| * or for some other reason cannot be opened for | |
| * reading. | |
| * @see java.io.File#getPath() | |
| */ | |
| @SuppressWarnings("this-escape") | |
| public FileInputStream(File file) throws FileNotFoundException { | |
| if (file.isInvalid()) { | |
| throw new FileNotFoundException("Invalid file path"); | |
| } | |
| path = file.getPath(); | |
| fd = new FileDescriptor(); | |
| fd.attach(this); | |
| open(path); | |
| FileCleanable.register(fd); // open set the fd, register the cleanup | |
| } | |
| /** | |
| * Creates a {@code FileInputStream} by using the file descriptor | |
| * {@code fdObj}, which represents an existing connection to an | |
| * actual file in the file system. | |
| * <p> | |
| * If {@code fdObj} is null then a {@code NullPointerException} | |
| * is thrown. | |
| * <p> | |
| * This constructor does not throw an exception if {@code fdObj} | |
| * is {@link java.io.FileDescriptor#valid() invalid}. | |
| * However, if the methods are invoked on the resulting stream to attempt | |
| * I/O on the stream, an {@code IOException} is thrown. | |
| * | |
| * @param fdObj the file descriptor to be opened for reading. | |
| */ | |
| @SuppressWarnings("this-escape") | |
| public FileInputStream(FileDescriptor fdObj) { | |
| if (fdObj == null) { | |
| throw new NullPointerException(); | |
| } | |
| fd = fdObj; | |
| path = null; | |
| /* | |
| * FileDescriptor is being shared by streams. | |
| * Register this stream with FileDescriptor tracker. | |
| */ | |
| fd.attach(this); | |
| } | |
| /** | |
| * Opens the specified file for reading. | |
| * @param name the name of the file | |
| */ | |
| private native void open0(String name) throws FileNotFoundException; | |
| // wrap native call to allow instrumentation | |
| /** | |
| * Opens the specified file for reading. | |
| * @param name the name of the file | |
| */ | |
| private void open(String name) throws FileNotFoundException { | |
| open0(name); | |
| } | |
| /** | |
| * Reads a byte of data from this input stream. This method blocks | |
| * if no input is yet available. | |
| * | |
| * @return the next byte of data, or {@code -1} if the end of the | |
| * file is reached. | |
| * @throws IOException {@inheritDoc} | |
| */ | |
| @Override | |
| public int read() throws IOException { | |
| if (jfrTracing && FileReadEvent.enabled()) { | |
| return traceRead0(); | |
| } | |
| return read0(); | |
| } | |
| private native int read0() throws IOException; | |
| private int traceRead0() throws IOException { | |
| int result = 0; | |
| long bytesRead = 0; | |
| long start = FileReadEvent.timestamp(); | |
| try { | |
| result = read0(); | |
| if (result < 0) { | |
| bytesRead = -1; | |
| } else { | |
| bytesRead = 1; | |
| } | |
| } finally { | |
| FileReadEvent.offer(start, path, bytesRead); | |
| } | |
| return result; | |
| } | |
| /** | |
| * Reads a subarray as a sequence of bytes. | |
| * @param b the data to be written | |
| * @param off the start offset in the data | |
| * @param len the number of bytes that are written | |
| * @throws IOException If an I/O error has occurred. | |
| */ | |
| private native int readBytes(byte[] b, int off, int len) throws IOException; | |
| private int traceReadBytes(byte b[], int off, int len) throws IOException { | |
| int bytesRead = 0; | |
| long start = FileReadEvent.timestamp(); | |
| try { | |
| bytesRead = readBytes(b, off, len); | |
| } finally { | |
| FileReadEvent.offer(start, path, bytesRead); | |
| } | |
| return bytesRead; | |
| } | |
| /** | |
| * Reads up to {@code b.length} bytes of data from this input | |
| * stream into an array of bytes. This method blocks until some input | |
| * is available. | |
| * | |
| * @param b {@inheritDoc} | |
| * @return the total number of bytes read into the buffer, or | |
| * {@code -1} if there is no more data because the end of | |
| * the file has been reached. | |
| * @throws IOException if an I/O error occurs. | |
| */ | |
| @Override | |
| public int read(byte[] b) throws IOException { | |
| if (jfrTracing && FileReadEvent.enabled()) { | |
| return traceReadBytes(b, 0, b.length); | |
| } | |
| return readBytes(b, 0, b.length); | |
| } | |
| /** | |
| * Reads up to {@code len} bytes of data from this input stream | |
| * into an array of bytes. If {@code len} is not zero, the method | |
| * blocks until some input is available; otherwise, no | |
| * bytes are read and {@code 0} is returned. | |
| * | |
| * @param b {@inheritDoc} | |
| * @param off {@inheritDoc} | |
| * @param len {@inheritDoc} | |
| * @return {@inheritDoc} | |
| * @throws NullPointerException {@inheritDoc} | |
| * @throws IndexOutOfBoundsException {@inheritDoc} | |
| * @throws IOException if an I/O error occurs. | |
| */ | |
| @Override | |
| public int read(byte[] b, int off, int len) throws IOException { | |
| if (jfrTracing && FileReadEvent.enabled()) { | |
| return traceReadBytes(b, off, len); | |
| } | |
| return readBytes(b, off, len); | |
| } | |
| @Override | |
| public byte[] readAllBytes() throws IOException { | |
| if (!isRegularFile()) | |
| return super.readAllBytes(); | |
| long length = length(); | |
| long position = position(); | |
| long size = length - position; | |
| if (length <= 0 || size <= 0) | |
| return super.readAllBytes(); | |
| if (size > (long) Integer.MAX_VALUE) { | |
| String msg = | |
| String.format("Required array size too large for %s: %d = %d - %d", | |
| path, size, length, position); | |
| throw new OutOfMemoryError(msg); | |
| } | |
| int capacity = (int)size; | |
| byte[] buf = new byte[capacity]; | |
| int nread = 0; | |
| int n; | |
| for (;;) { | |
| // read to EOF which may read more or less than initial size, e.g., | |
| // file is truncated while we are reading | |
| while ((n = read(buf, nread, capacity - nread)) > 0) | |
| nread += n; | |
| // if last call to read() returned -1, we are done; otherwise, | |
| // try to read one more byte and if that fails we're done too | |
| if (n < 0 || (n = read()) < 0) | |
| break; | |
| // one more byte was read; need to allocate a larger buffer | |
| capacity = Math.max(ArraysSupport.newLength(capacity, | |
| 1, // min growth | |
| capacity), // pref growth | |
| DEFAULT_BUFFER_SIZE); | |
| buf = Arrays.copyOf(buf, capacity); | |
| buf[nread++] = (byte)n; | |
| } | |
| return (capacity == nread) ? buf : Arrays.copyOf(buf, nread); | |
| } | |
| /** | |
| * @since 11 | |
| */ | |
| @Override | |
| public byte[] readNBytes(int len) throws IOException { | |
| if (len < 0) | |
| throw new IllegalArgumentException("len < 0"); | |
| if (len == 0) | |
| return new byte[0]; | |
| if (!isRegularFile()) | |
| return super.readNBytes(len); | |
| long length = length(); | |
| long position = position(); | |
| long size = length - position; | |
| if (length <= 0 || size <= 0) | |
| return super.readNBytes(len); | |
| int capacity = (int)Math.min(len, size); | |
| byte[] buf = new byte[capacity]; | |
| int remaining = capacity; | |
| int nread = 0; | |
| int n; | |
| do { | |
| n = read(buf, nread, remaining); | |
| if (n > 0) { | |
| nread += n; | |
| remaining -= n; | |
| } else if (n == 0) { | |
| // Block until a byte is read or EOF is detected | |
| byte b = (byte)read(); | |
| if (b == -1 ) | |
| break; | |
| buf[nread++] = b; | |
| remaining--; | |
| } | |
| } while (n >= 0 && remaining > 0); | |
| return (capacity == nread) ? buf : Arrays.copyOf(buf, nread); | |
| } | |
| /** | |
| * {@inheritDoc} | |
| */ | |
| @Override | |
| public long transferTo(OutputStream out) throws IOException { | |
| long transferred = 0L; | |
| if (out instanceof FileOutputStream fos && isRegularFile()) { | |
| FileChannel fc = getChannel(); | |
| long pos = fc.position(); | |
| transferred = fc.transferTo(pos, Long.MAX_VALUE, fos.getChannel()); | |
| long newPos = pos + transferred; | |
| fc.position(newPos); | |
| if (newPos >= fc.size()) { | |
| return transferred; | |
| } | |
| } | |
| try { | |
| return Math.addExact(transferred, super.transferTo(out)); | |
| } catch (ArithmeticException ignore) { | |
| return Long.MAX_VALUE; | |
| } | |
| } | |
| private long length() throws IOException { | |
| return length0(); | |
| } | |
| private native long length0() throws IOException; | |
| private long position() throws IOException { | |
| return position0(); | |
| } | |
| private native long position0() throws IOException; | |
| /** | |
| * Skips over and discards {@code n} bytes of data from the | |
| * input stream. | |
| * | |
| * <p>The {@code skip} method may, for a variety of | |
| * reasons, end up skipping over some smaller number of bytes, | |
| * possibly {@code 0}. If {@code n} is negative, the method | |
| * will try to skip backwards. In case the backing file does not support | |
| * backward skip at its current position, an {@code IOException} is | |
| * thrown. The actual number of bytes skipped is returned. If it skips | |
| * forwards, it returns a positive value. If it skips backwards, it | |
| * returns a negative value. | |
| * | |
| * <p>This method may skip more bytes than what are remaining in the | |
| * backing file. This produces no exception and the number of bytes skipped | |
| * may include some number of bytes that were beyond the EOF of the | |
| * backing file. Attempting to read from the stream after skipping past | |
| * the end will result in -1 indicating the end of the file. | |
| * | |
| * @param n {@inheritDoc} | |
| * @return the actual number of bytes skipped. | |
| * @throws IOException if n is negative, if the stream does not | |
| * support seek, or if an I/O error occurs. | |
| */ | |
| @Override | |
| public long skip(long n) throws IOException { | |
| if (isRegularFile()) | |
| return skip0(n); | |
| return super.skip(n); | |
| } | |
| private native long skip0(long n) throws IOException; | |
| /** | |
| * Returns an estimate of the number of remaining bytes that can be read (or | |
| * skipped over) from this input stream without blocking by the next | |
| * invocation of a method for this input stream. Returns 0 when the file | |
| * position is beyond EOF. The next invocation might be the same thread | |
| * or another thread. A single read or skip of this many bytes will not | |
| * block, but may read or skip fewer bytes. | |
| * | |
| * <p> In some cases, a non-blocking read (or skip) may appear to be | |
| * blocked when it is merely slow, for example when reading large | |
| * files over slow networks. | |
| * | |
| * @return an estimate of the number of remaining bytes that can be read | |
| * (or skipped over) from this input stream without blocking. | |
| * @throws IOException if this file input stream has been closed by calling | |
| * {@code close} or an I/O error occurs. | |
| */ | |
| @Override | |
| public int available() throws IOException { | |
| return available0(); | |
| } | |
| private native int available0() throws IOException; | |
| /** | |
| * Closes this file input stream and releases any system resources | |
| * associated with the stream. | |
| * | |
| * <p> If this stream has an associated channel then the channel is closed | |
| * as well. | |
| * | |
| * @apiNote | |
| * Overriding {@link #close} to perform cleanup actions is reliable | |
| * only when called directly or when called by try-with-resources. | |
| * | |
| * @implSpec | |
| * Subclasses requiring that resource cleanup take place after a stream becomes | |
| * unreachable should use the {@link java.lang.ref.Cleaner} mechanism. | |
| * | |
| * <p> | |
| * If this stream has an associated channel then this method will close the | |
| * channel, which in turn will close this stream. Subclasses that override | |
| * this method should be prepared to handle possible reentrant invocation. | |
| * | |
| * @throws IOException {@inheritDoc} | |
| */ | |
| @Override | |
| public void close() throws IOException { | |
| if (closed) { | |
| return; | |
| } | |
| synchronized (closeLock) { | |
| if (closed) { | |
| return; | |
| } | |
| closed = true; | |
| } | |
| FileChannel fc = channel; | |
| if (fc != null) { | |
| // possible race with getChannel(), benign since | |
| // FileChannel.close is final and idempotent | |
| fc.close(); | |
| } | |
| fd.closeAll(new Closeable() { | |
| public void close() throws IOException { | |
| fd.close(); | |
| } | |
| }); | |
| } | |
| /** | |
| * Returns the {@code FileDescriptor} | |
| * object that represents the connection to | |
| * the actual file in the file system being | |
| * used by this {@code FileInputStream}. | |
| * | |
| * @return the file descriptor object associated with this stream. | |
| * @throws IOException if an I/O error occurs. | |
| * @see java.io.FileDescriptor | |
| */ | |
| public final FileDescriptor getFD() throws IOException { | |
| if (fd != null) { | |
| return fd; | |
| } | |
| throw new IOException(); | |
| } | |
| /** | |
| * Returns the unique {@link java.nio.channels.FileChannel FileChannel} | |
| * object associated with this file input stream. | |
| * | |
| * <p> The initial {@link java.nio.channels.FileChannel#position() | |
| * position} of the returned channel will be equal to the | |
| * number of bytes read from the file so far. Reading bytes from this | |
| * stream will increment the channel's position. Changing the channel's | |
| * position, either explicitly or by reading, will change this stream's | |
| * file position. | |
| * | |
| * @return the file channel associated with this file input stream | |
| * | |
| * @since 1.4 | |
| */ | |
| public FileChannel getChannel() { | |
| FileChannel fc = this.channel; | |
| if (fc == null) { | |
| synchronized (this) { | |
| fc = this.channel; | |
| if (fc == null) { | |
| fc = FileChannelImpl.open(fd, path, true, false, false, false, this); | |
| this.channel = fc; | |
| if (closed) { | |
| try { | |
| // possible race with close(), benign since | |
| // FileChannel.close is final and idempotent | |
| fc.close(); | |
| } catch (IOException ioe) { | |
| throw new InternalError(ioe); // should not happen | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return fc; | |
| } | |
| /** | |
| * Determine whether the file is a regular file. | |
| */ | |
| private boolean isRegularFile() { | |
| Boolean isRegularFile = this.isRegularFile; | |
| if (isRegularFile == null) { | |
| this.isRegularFile = isRegularFile = isRegularFile0(fd); | |
| } | |
| return isRegularFile; | |
| } | |
| private native boolean isRegularFile0(FileDescriptor fd); | |
| private static native void initIDs(); | |
| static { | |
| initIDs(); | |
| } | |
| } |