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;
21import java.nio.NioUtils;
22import java.nio.channels.FileChannel;
23import java.util.Arrays;
24import libcore.io.IoBridge;
25import libcore.io.IoUtils;
26import static libcore.io.OsConstants.*;
27
28/**
29 * An output stream that writes bytes to a file. If the output file exists, it
30 * can be replaced or appended to. If it does not exist, a new file will be
31 * created.
32 * <pre>   {@code
33 *   File file = ...
34 *   OutputStream out = null;
35 *   try {
36 *     out = new BufferedOutputStream(new FileOutputStream(file));
37 *     ...
38 *   } finally {
39 *     if (out != null) {
40 *       out.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 BufferedOutputStream}.
47 *
48 * <p>Use {@link FileWriter} to write characters, as opposed to bytes, to a file.
49 *
50 * @see BufferedOutputStream
51 * @see FileInputStream
52 */
53public class FileOutputStream extends OutputStream implements Closeable {
54
55    private FileDescriptor fd;
56    private final boolean shouldClose;
57
58    /** The unique file channel. Lazily initialized because it's rarely needed. */
59    private FileChannel channel;
60
61    /** File access mode */
62    private final int mode;
63
64    private final CloseGuard guard = CloseGuard.get();
65
66    /**
67     * Constructs a new {@code FileOutputStream} that writes to {@code file}. The file will be
68     * truncated if it exists, and created if it doesn't exist.
69     *
70     * @throws FileNotFoundException if file cannot be opened for writing.
71     */
72    public FileOutputStream(File file) throws FileNotFoundException {
73        this(file, false);
74    }
75
76    /**
77     * Constructs a new {@code FileOutputStream} that writes to {@code file}.
78     * If {@code append} is true and the file already exists, it will be appended to; otherwise
79     * it will be truncated. The file will be created if it does not exist.
80     *
81     * @throws FileNotFoundException if the file cannot be opened for writing.
82     */
83    public FileOutputStream(File file, boolean append) throws FileNotFoundException {
84        if (file == null) {
85            throw new NullPointerException("file == null");
86        }
87        this.mode = O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC);
88        this.fd = IoBridge.open(file.getAbsolutePath(), mode);
89        this.shouldClose = true;
90        this.guard.open("close");
91    }
92
93    /**
94     * Constructs a new {@code FileOutputStream} that writes to {@code fd}.
95     *
96     * @throws NullPointerException if {@code fd} is null.
97     */
98    public FileOutputStream(FileDescriptor fd) {
99        if (fd == null) {
100            throw new NullPointerException("fd == null");
101        }
102        this.fd = fd;
103        this.shouldClose = false;
104        this.mode = O_WRONLY;
105        this.channel = NioUtils.newFileChannel(this, fd, mode);
106        // Note that we do not call guard.open here because the
107        // FileDescriptor is not owned by the stream.
108    }
109
110    /**
111     * Constructs a new {@code FileOutputStream} that writes to {@code path}. The file will be
112     * truncated if it exists, and created if it doesn't exist.
113     *
114     * @throws FileNotFoundException if file cannot be opened for writing.
115     */
116    public FileOutputStream(String path) throws FileNotFoundException {
117        this(path, false);
118    }
119
120    /**
121     * Constructs a new {@code FileOutputStream} that writes to {@code path}.
122     * If {@code append} is true and the file already exists, it will be appended to; otherwise
123     * it will be truncated. The file will be created if it does not exist.
124     *
125     * @throws FileNotFoundException if the file cannot be opened for writing.
126     */
127    public FileOutputStream(String path, boolean append) throws FileNotFoundException {
128        this(new File(path), append);
129    }
130
131    @Override
132    public void close() throws IOException {
133        guard.close();
134        synchronized (this) {
135            if (channel != null) {
136                channel.close();
137            }
138            if (shouldClose) {
139                IoUtils.close(fd);
140            } else {
141                // An owned fd has been invalidated by IoUtils.close, but
142                // we need to explicitly stop using an unowned fd (http://b/4361076).
143                fd = new FileDescriptor();
144            }
145        }
146    }
147
148    @Override protected void finalize() throws IOException {
149        try {
150            if (guard != null) {
151                guard.warnIfOpen();
152            }
153            close();
154        } finally {
155            try {
156                super.finalize();
157            } catch (Throwable t) {
158                // for consistency with the RI, we must override Object.finalize() to
159                // remove the 'throws Throwable' clause.
160                throw new AssertionError(t);
161            }
162        }
163    }
164
165    /**
166     * Returns a write-only {@link FileChannel} that shares its position with
167     * this stream.
168     */
169    public FileChannel getChannel() {
170        synchronized (this) {
171            if (channel == null) {
172                channel = NioUtils.newFileChannel(this, fd, mode);
173            }
174            return channel;
175        }
176    }
177
178    /**
179     * Returns the underlying file descriptor.
180     */
181    public final FileDescriptor getFD() throws IOException {
182        return fd;
183    }
184
185    @Override
186    public void write(byte[] buffer, int byteOffset, int byteCount) throws IOException {
187        IoBridge.write(fd, buffer, byteOffset, byteCount);
188    }
189
190    @Override
191    public void write(int oneByte) throws IOException {
192        write(new byte[] { (byte) oneByte }, 0, 1);
193    }
194}
195