blob: 537607a3239df611e3f805f657d7fc648e343805 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package java.io;
import dalvik.system.CloseGuard;
import java.nio.NioUtils;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import libcore.io.ErrnoException;
import libcore.io.IoBridge;
import libcore.io.IoUtils;
import libcore.io.Libcore;
import libcore.io.Streams;
import static libcore.io.OsConstants.*;
/**
* An input stream that reads bytes from a file.
* <pre> {@code
* File file = ...
* InputStream in = null;
* try {
* in = new BufferedInputStream(new FileInputStream(file));
* ...
* } finally {
* if (in != null) {
* in.close();
* }
* }
* }</pre>
*
* <p>This stream is <strong>not buffered</strong>. Most callers should wrap
* this stream with a {@link BufferedInputStream}.
*
* <p>Use {@link FileReader} to read characters, as opposed to bytes, from a
* file.
*
* @see BufferedInputStream
* @see FileOutputStream
*/
public class FileInputStream extends InputStream implements Closeable {
private FileDescriptor fd;
private final boolean shouldClose;
/** The unique file channel. Lazily initialized because it's rarely needed. */
private FileChannel channel;
private final CloseGuard guard = CloseGuard.get();
/**
* Constructs a new {@code FileInputStream} that reads from {@code file}.
*
* @param file
* the file from which this stream reads.
* @throws FileNotFoundException
* if {@code file} does not exist.
*/
public FileInputStream(File file) throws FileNotFoundException {
if (file == null) {
throw new NullPointerException("file == null");
}
this.fd = IoBridge.open(file.getAbsolutePath(), O_RDONLY);
this.shouldClose = true;
guard.open("close");
}
/**
* Constructs a new {@code FileInputStream} that reads from {@code fd}.
*
* @param fd
* the FileDescriptor from which this stream reads.
* @throws NullPointerException
* if {@code fd} is {@code null}.
*/
public FileInputStream(FileDescriptor fd) {
if (fd == null) {
throw new NullPointerException("fd == null");
}
this.fd = fd;
this.shouldClose = false;
// Note that we do not call guard.open here because the
// FileDescriptor is not owned by the stream.
}
/**
* Equivalent to {@code new FileInputStream(new File(path))}.
*/
public FileInputStream(String path) throws FileNotFoundException {
this(new File(path));
}
@Override
public int available() throws IOException {
return IoBridge.available(fd);
}
@Override
public void close() throws IOException {
guard.close();
synchronized (this) {
if (channel != null) {
channel.close();
}
if (shouldClose) {
IoUtils.close(fd);
} else {
// An owned fd has been invalidated by IoUtils.close, but
// we need to explicitly stop using an unowned fd (http://b/4361076).
fd = new FileDescriptor();
}
}
}
/**
* Ensures that all resources for this stream are released when it is about
* to be garbage collected.
*
* @throws IOException
* if an error occurs attempting to finalize this stream.
*/
@Override protected void finalize() throws IOException {
try {
if (guard != null) {
guard.warnIfOpen();
}
close();
} finally {
try {
super.finalize();
} catch (Throwable t) {
// for consistency with the RI, we must override Object.finalize() to
// remove the 'throws Throwable' clause.
throw new AssertionError(t);
}
}
}
/**
* Returns a read-only {@link FileChannel} that shares its position with
* this stream.
*/
public FileChannel getChannel() {
synchronized (this) {
if (channel == null) {
channel = NioUtils.newFileChannel(this, fd, O_RDONLY);
}
return channel;
}
}
/**
* Returns the underlying file descriptor.
*/
public final FileDescriptor getFD() throws IOException {
return fd;
}
@Override public int read() throws IOException {
return Streams.readSingleByte(this);
}
@Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
return IoBridge.read(fd, buffer, byteOffset, byteCount);
}
@Override
public long skip(long byteCount) throws IOException {
if (byteCount < 0) {
throw new IOException("byteCount < 0: " + byteCount);
}
try {
// Try lseek(2). That returns the new offset, but we'll throw an
// exception if it couldn't perform exactly the seek we asked for.
Libcore.os.lseek(fd, byteCount, SEEK_CUR);
return byteCount;
} catch (ErrnoException errnoException) {
if (errnoException.errno == ESPIPE) {
// You can't seek on a pipe, so fall back to the superclass' implementation.
return super.skip(byteCount);
}
throw errnoException.rethrowAsIOException();
}
}
}