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 java.util.Arrays;
21import libcore.util.SneakyThrow;
22
23/**
24 * Wraps an existing {@link Writer} and <em>buffers</em> the output. Expensive
25 * interaction with the underlying reader is minimized, since most (smaller)
26 * requests can be satisfied by accessing the buffer alone. The drawback is that
27 * some extra space is required to hold the buffer and that copying takes place
28 * when filling that buffer, but this is usually outweighed by the performance
29 * benefits.
30 *
31 * <p/>A typical application pattern for the class looks like this:<p/>
32 *
33 * <pre>
34 * BufferedWriter buf = new BufferedWriter(new FileWriter(&quot;file.java&quot;));
35 * </pre>
36 *
37 * @see BufferedReader
38 */
39public class BufferedWriter extends Writer {
40
41    private Writer out;
42
43    private char[] buf;
44
45    private int pos;
46
47    /**
48     * Constructs a new {@code BufferedWriter}, providing {@code out} with a buffer
49     * of 8192 bytes.
50     *
51     * @param out the {@code Writer} the buffer writes to.
52     */
53    public BufferedWriter(Writer out) {
54        this(out, 8192);
55    }
56
57    /**
58     * Constructs a new {@code BufferedWriter}, providing {@code out} with {@code size} bytes
59     * of buffer.
60     *
61     * @param out the {@code OutputStream} the buffer writes to.
62     * @param size the size of buffer in bytes.
63     * @throws IllegalArgumentException if {@code size <= 0}.
64     */
65    public BufferedWriter(Writer out, int size) {
66        super(out);
67        if (size <= 0) {
68            throw new IllegalArgumentException("size <= 0");
69        }
70        this.out = out;
71        this.buf = new char[size];
72    }
73
74    /**
75     * Closes this writer. The contents of the buffer are flushed, the target
76     * writer is closed, and the buffer is released. Only the first invocation
77     * of close has any effect.
78     *
79     * @throws IOException
80     *             if an error occurs while closing this writer.
81     */
82    @Override
83    public void close() throws IOException {
84        synchronized (lock) {
85            if (isClosed()) {
86                return;
87            }
88
89            Throwable thrown = null;
90            try {
91                flushInternal();
92            } catch (Throwable e) {
93                thrown = e;
94            }
95            buf = null;
96
97            try {
98                out.close();
99            } catch (Throwable e) {
100                if (thrown == null) {
101                    thrown = e;
102                }
103            }
104            out = null;
105
106            if (thrown != null) {
107                SneakyThrow.sneakyThrow(thrown);
108            }
109        }
110    }
111
112    /**
113     * Flushes this writer. The contents of the buffer are committed to the
114     * target writer and it is then flushed.
115     *
116     * @throws IOException
117     *             if an error occurs while flushing this writer.
118     */
119    @Override
120    public void flush() throws IOException {
121        synchronized (lock) {
122            checkNotClosed();
123            flushInternal();
124            out.flush();
125        }
126    }
127
128    private void checkNotClosed() throws IOException {
129        if (isClosed()) {
130            throw new IOException("BufferedWriter is closed");
131        }
132    }
133
134    /**
135     * Flushes the internal buffer.
136     */
137    private void flushInternal() throws IOException {
138        if (pos > 0) {
139            out.write(buf, 0, pos);
140        }
141        pos = 0;
142    }
143
144    /**
145     * Indicates whether this writer is closed.
146     *
147     * @return {@code true} if this writer is closed, {@code false} otherwise.
148     */
149    private boolean isClosed() {
150        return out == null;
151    }
152
153    /**
154     * Writes a newline to this writer. On Android, this is {@code "\n"}.
155     * The target writer may or may not be flushed when a newline is written.
156     *
157     * @throws IOException
158     *             if an error occurs attempting to write to this writer.
159     */
160    public void newLine() throws IOException {
161        write(System.lineSeparator());
162    }
163
164    /**
165     * Writes {@code count} characters starting at {@code offset} in
166     * {@code buffer} to this writer. If {@code count} is greater than this
167     * writer's buffer, then the buffer is flushed and the characters are
168     * written directly to the target writer.
169     *
170     * @param buffer
171     *            the array containing characters to write.
172     * @param offset
173     *            the start position in {@code buffer} for retrieving characters.
174     * @param count
175     *            the maximum number of characters to write.
176     * @throws IndexOutOfBoundsException
177     *             if {@code offset < 0} or {@code count < 0}, or if
178     *             {@code offset + count} is greater than the size of
179     *             {@code buffer}.
180     * @throws IOException
181     *             if this writer is closed or another I/O error occurs.
182     */
183    @Override
184    public void write(char[] buffer, int offset, int count) throws IOException {
185        synchronized (lock) {
186            checkNotClosed();
187            if (buffer == null) {
188                throw new NullPointerException("buffer == null");
189            }
190            Arrays.checkOffsetAndCount(buffer.length, offset, count);
191            if (pos == 0 && count >= this.buf.length) {
192                out.write(buffer, offset, count);
193                return;
194            }
195            int available = this.buf.length - pos;
196            if (count < available) {
197                available = count;
198            }
199            if (available > 0) {
200                System.arraycopy(buffer, offset, this.buf, pos, available);
201                pos += available;
202            }
203            if (pos == this.buf.length) {
204                out.write(this.buf, 0, this.buf.length);
205                pos = 0;
206                if (count > available) {
207                    offset += available;
208                    available = count - available;
209                    if (available >= this.buf.length) {
210                        out.write(buffer, offset, available);
211                        return;
212                    }
213
214                    System.arraycopy(buffer, offset, this.buf, pos, available);
215                    pos += available;
216                }
217            }
218        }
219    }
220
221    /**
222     * Writes the character {@code oneChar} to this writer. If the buffer
223     * gets full by writing this character, this writer is flushed. Only the
224     * lower two bytes of the integer {@code oneChar} are written.
225     *
226     * @param oneChar
227     *            the character to write.
228     * @throws IOException
229     *             if this writer is closed or another I/O error occurs.
230     */
231    @Override
232    public void write(int oneChar) throws IOException {
233        synchronized (lock) {
234            checkNotClosed();
235            if (pos >= buf.length) {
236                out.write(buf, 0, buf.length);
237                pos = 0;
238            }
239            buf[pos++] = (char) oneChar;
240        }
241    }
242
243    /**
244     * Writes {@code count} characters starting at {@code offset} in {@code str}
245     * to this writer. If {@code count} is greater than this writer's buffer,
246     * then this writer is flushed and the remaining characters are written
247     * directly to the target writer. If count is negative no characters are
248     * written to the buffer. This differs from the behavior of the superclass.
249     *
250     * @param str
251     *            the non-null String containing characters to write.
252     * @param offset
253     *            the start position in {@code str} for retrieving characters.
254     * @param count
255     *            maximum number of characters to write.
256     * @throws IOException
257     *             if this writer has already been closed or another I/O error
258     *             occurs.
259     * @throws IndexOutOfBoundsException
260     *             if {@code offset < 0} or {@code offset + count} is greater
261     *             than the length of {@code str}.
262     */
263    @Override
264    public void write(String str, int offset, int count) throws IOException {
265        synchronized (lock) {
266            checkNotClosed();
267            if (count <= 0) {
268                return;
269            }
270            if (offset < 0 || offset > str.length() - count) {
271                throw new StringIndexOutOfBoundsException(str, offset, count);
272            }
273            if (pos == 0 && count >= buf.length) {
274                char[] chars = new char[count];
275                str.getChars(offset, offset + count, chars, 0);
276                out.write(chars, 0, count);
277                return;
278            }
279            int available = buf.length - pos;
280            if (count < available) {
281                available = count;
282            }
283            if (available > 0) {
284                str.getChars(offset, offset + available, buf, pos);
285                pos += available;
286            }
287            if (pos == buf.length) {
288                out.write(this.buf, 0, this.buf.length);
289                pos = 0;
290                if (count > available) {
291                    offset += available;
292                    available = count - available;
293                    if (available >= buf.length) {
294                        char[] chars = new char[count];
295                        str.getChars(offset, offset + available, chars, 0);
296                        out.write(chars, 0, available);
297                        return;
298                    }
299                    str.getChars(offset, offset + available, buf, pos);
300                    pos += available;
301                }
302            }
303        }
304    }
305}
306