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;
21
22/**
23 * Wraps an existing {@link OutputStream} and <em>buffers</em> the output.
24 * Expensive interaction with the underlying input stream is minimized, since
25 * most (smaller) requests can be satisfied by accessing the buffer alone. The
26 * drawback is that some extra space is required to hold the buffer and that
27 * copying takes place when flushing that buffer, but this is usually outweighed
28 * by the performance benefits.
29 *
30 * <p/>A typical application pattern for the class looks like this:<p/>
31 *
32 * <pre>
33 * BufferedOutputStream buf = new BufferedOutputStream(new FileOutputStream(&quot;file.java&quot;));
34 * </pre>
35 *
36 * @see BufferedInputStream
37 */
38public class BufferedOutputStream extends FilterOutputStream {
39    /**
40     * The buffer containing the bytes to be written to the target stream.
41     */
42    protected byte[] buf;
43
44    /**
45     * The total number of bytes inside the byte array {@code buf}.
46     */
47    protected int count;
48
49    /**
50     * Constructs a new {@code BufferedOutputStream}, providing {@code out} with a buffer
51     * of 8192 bytes.
52     *
53     * @param out the {@code OutputStream} the buffer writes to.
54     */
55    public BufferedOutputStream(OutputStream out) {
56        this(out, 8192);
57    }
58
59    /**
60     * Constructs a new {@code BufferedOutputStream}, providing {@code out} with {@code size} bytes
61     * of buffer.
62     *
63     * @param out the {@code OutputStream} the buffer writes to.
64     * @param size the size of buffer in bytes.
65     * @throws IllegalArgumentException if {@code size <= 0}.
66     */
67    public BufferedOutputStream(OutputStream out, int size) {
68        super(out);
69        if (size <= 0) {
70            throw new IllegalArgumentException("size <= 0");
71        }
72        buf = new byte[size];
73    }
74
75    /**
76     * Flushes this stream to ensure all pending data is written out to the
77     * target stream. In addition, the target stream is flushed.
78     *
79     * @throws IOException
80     *             if an error occurs attempting to flush this stream.
81     */
82    @Override
83    public synchronized void flush() throws IOException {
84        checkNotClosed();
85        flushInternal();
86        out.flush();
87    }
88
89    private void checkNotClosed() throws IOException {
90        if (buf == null) {
91            throw new IOException("BufferedOutputStream is closed");
92        }
93    }
94
95    /**
96     * Writes {@code count} bytes from the byte array {@code buffer} starting at
97     * {@code offset} to this stream. If there is room in the buffer to hold the
98     * bytes, they are copied in. If not, the buffered bytes plus the bytes in
99     * {@code buffer} are written to the target stream, the target is flushed,
100     * and the buffer is cleared.
101     *
102     * @param buffer
103     *            the buffer to be written.
104     * @param offset
105     *            the start position in {@code buffer} from where to get bytes.
106     * @param length
107     *            the number of bytes from {@code buffer} to write to this
108     *            stream.
109     * @throws IndexOutOfBoundsException
110     *             if {@code offset < 0} or {@code length < 0}, or if
111     *             {@code offset + length} is greater than the size of
112     *             {@code buffer}.
113     * @throws IOException
114     *             if an error occurs attempting to write to this stream.
115     * @throws NullPointerException
116     *             if {@code buffer} is {@code null}.
117     * @throws ArrayIndexOutOfBoundsException
118     *             If offset or count is outside of bounds.
119     */
120    @Override
121    public synchronized void write(byte[] buffer, int offset, int length) throws IOException {
122        checkNotClosed();
123
124        if (buffer == null) {
125            throw new NullPointerException("buffer == null");
126        }
127
128        byte[] internalBuffer = buf;
129        if (length >= internalBuffer.length) {
130            flushInternal();
131            out.write(buffer, offset, length);
132            return;
133        }
134
135        Arrays.checkOffsetAndCount(buffer.length, offset, length);
136
137        // flush the internal buffer first if we have not enough space left
138        if (length > (internalBuffer.length - count)) {
139            flushInternal();
140        }
141
142        System.arraycopy(buffer, offset, internalBuffer, count, length);
143        count += length;
144    }
145
146    @Override public synchronized void close() throws IOException {
147        if (buf == null) {
148            return;
149        }
150
151        try {
152            super.close();
153        } finally {
154            buf = null;
155        }
156    }
157
158    /**
159     * Writes one byte to this stream. Only the low order byte of the integer
160     * {@code oneByte} is written. If there is room in the buffer, the byte is
161     * copied into the buffer and the count incremented. Otherwise, the buffer
162     * plus {@code oneByte} are written to the target stream, the target is
163     * flushed, and the buffer is reset.
164     *
165     * @param oneByte
166     *            the byte to be written.
167     * @throws IOException
168     *             if an error occurs attempting to write to this stream.
169     */
170    @Override
171    public synchronized void write(int oneByte) throws IOException {
172        checkNotClosed();
173        if (count == buf.length) {
174            out.write(buf, 0, count);
175            count = 0;
176        }
177        buf[count++] = (byte) oneByte;
178    }
179
180    /**
181     * Flushes only internal buffer.
182     */
183    private void flushInternal() throws IOException {
184        if (count > 0) {
185            out.write(buf, 0, count);
186            count = 0;
187        }
188    }
189}
190