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
18package java.io;
19
20import dalvik.system.CloseGuard;
21
22import android.system.ErrnoException;
23import java.nio.channels.FileChannel;
24import java.nio.NioUtils;
25import libcore.io.IoBridge;
26import libcore.io.Libcore;
27import libcore.io.Streams;
28import static android.system.OsConstants.*;
29
30/**
31 * An input stream that reads bytes from a file.
32 * <pre>   {@code
33 *   File file = ...
34 *   InputStream in = null;
35 *   try {
36 *     in = new BufferedInputStream(new FileInputStream(file));
37 *     ...
38 *   } finally {
39 *     if (in != null) {
40 *       in.close();
41 *     }
42 *   }
43 * }</pre>
44 *
45 * <p>This stream is <strong>not buffered</strong>. Most callers should wrap
46 * this stream with a {@link BufferedInputStream}.
47 *
48 * <p>Use {@link FileReader} to read characters, as opposed to bytes, from a
49 * file.
50 *
51 * @see BufferedInputStream
52 * @see FileOutputStream
53 */
54public class FileInputStream extends InputStream {
55
56    private FileDescriptor fd;
57    private final boolean shouldClose;
58
59    /** The unique file channel. Lazily initialized because it's rarely needed. */
60    private FileChannel channel;
61
62    private final CloseGuard guard = CloseGuard.get();
63
64    /**
65     * Constructs a new {@code FileInputStream} that reads from {@code file}.
66     *
67     * @param file
68     *            the file from which this stream reads.
69     * @throws FileNotFoundException
70     *             if {@code file} does not exist.
71     */
72    public FileInputStream(File file) throws FileNotFoundException {
73        if (file == null) {
74            throw new NullPointerException("file == null");
75        }
76        this.fd = IoBridge.open(file.getPath(), O_RDONLY);
77        this.shouldClose = true;
78        guard.open("close");
79    }
80
81    /**
82     * Constructs a new {@code FileInputStream} that reads from {@code fd}.
83     *
84     * @param fd
85     *            the FileDescriptor from which this stream reads.
86     * @throws NullPointerException
87     *             if {@code fd} is {@code null}.
88     */
89    public FileInputStream(FileDescriptor fd) {
90        if (fd == null) {
91            throw new NullPointerException("fd == null");
92        }
93        this.fd = fd;
94        this.shouldClose = false;
95        // Note that we do not call guard.open here because the
96        // FileDescriptor is not owned by the stream.
97    }
98
99    /**
100     * Equivalent to {@code new FileInputStream(new File(path))}.
101     */
102    public FileInputStream(String path) throws FileNotFoundException {
103        this(new File(path));
104    }
105
106    @Override
107    public int available() throws IOException {
108        return IoBridge.available(fd);
109    }
110
111    @Override
112    public void close() throws IOException {
113        guard.close();
114        synchronized (this) {
115            if (channel != null) {
116                channel.close();
117            }
118            if (shouldClose) {
119                IoBridge.closeAndSignalBlockedThreads(fd);
120            } else {
121                // An owned fd has been invalidated by IoUtils.close, but
122                // we need to explicitly stop using an unowned fd (http://b/4361076).
123                fd = new FileDescriptor();
124            }
125        }
126    }
127
128    /**
129     * Ensures that all resources for this stream are released when it is about
130     * to be garbage collected.
131     *
132     * @throws IOException
133     *             if an error occurs attempting to finalize this stream.
134     */
135    @Override protected void finalize() throws IOException {
136        try {
137            if (guard != null) {
138                guard.warnIfOpen();
139            }
140            close();
141        } finally {
142            try {
143                super.finalize();
144            } catch (Throwable t) {
145                // for consistency with the RI, we must override Object.finalize() to
146                // remove the 'throws Throwable' clause.
147                throw new AssertionError(t);
148            }
149        }
150    }
151
152    /**
153     * Returns a read-only {@link FileChannel} that shares its position with
154     * this stream.
155     */
156    public FileChannel getChannel() {
157        synchronized (this) {
158            if (channel == null) {
159                channel = NioUtils.newFileChannel(this, fd, O_RDONLY);
160            }
161            return channel;
162        }
163    }
164
165    /**
166     * Returns the underlying file descriptor.
167     */
168    public final FileDescriptor getFD() throws IOException {
169        return fd;
170    }
171
172    @Override public int read() throws IOException {
173        return Streams.readSingleByte(this);
174    }
175
176    @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
177        return IoBridge.read(fd, buffer, byteOffset, byteCount);
178    }
179
180    @Override
181    public long skip(long byteCount) throws IOException {
182        if (byteCount < 0) {
183            throw new IOException("byteCount < 0: " + byteCount);
184        }
185        try {
186            // Try lseek(2). That returns the new offset, but we'll throw an
187            // exception if it couldn't perform exactly the seek we asked for.
188            Libcore.os.lseek(fd, byteCount, SEEK_CUR);
189            return byteCount;
190        } catch (ErrnoException errnoException) {
191            if (errnoException.errno == ESPIPE) {
192                // You can't seek on a pipe, so fall back to the superclass' implementation.
193                return super.skip(byteCount);
194            }
195            throw errnoException.rethrowAsIOException();
196        }
197    }
198}
199