• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
18 package java.io;
19 
20 import java.nio.ByteBuffer;
21 import java.nio.CharBuffer;
22 import java.nio.charset.Charset;
23 import java.nio.charset.CharsetEncoder;
24 import java.nio.charset.CoderResult;
25 import java.nio.charset.CodingErrorAction;
26 import java.util.Arrays;
27 
28 /**
29  * A class for turning a character stream into a byte stream. Data written to
30  * the target input stream is converted into bytes by either a default or a
31  * provided character converter. The default encoding is taken from the
32  * "file.encoding" system property. {@code OutputStreamWriter} contains a buffer
33  * of bytes to be written to target stream and converts these into characters as
34  * needed. The buffer size is 8K.
35  *
36  * @see InputStreamReader
37  */
38 public class OutputStreamWriter extends Writer {
39 
40     private final OutputStream out;
41 
42     private CharsetEncoder encoder;
43 
44     private ByteBuffer bytes = ByteBuffer.allocate(8192);
45 
46     /**
47      * Constructs a new OutputStreamWriter using {@code out} as the target
48      * stream to write converted characters to. The default character encoding
49      * is used.
50      *
51      * @param out
52      *            the non-null target stream to write converted bytes to.
53      */
OutputStreamWriter(OutputStream out)54     public OutputStreamWriter(OutputStream out) {
55         this(out, Charset.defaultCharset());
56     }
57 
58     /**
59      * Constructs a new OutputStreamWriter using {@code out} as the target
60      * stream to write converted characters to and {@code enc} as the character
61      * encoding. If the encoding cannot be found, an
62      * UnsupportedEncodingException error is thrown.
63      *
64      * @param out
65      *            the target stream to write converted bytes to.
66      * @param enc
67      *            the string describing the desired character encoding.
68      * @throws NullPointerException
69      *             if {@code enc} is {@code null}.
70      * @throws UnsupportedEncodingException
71      *             if the encoding specified by {@code enc} cannot be found.
72      */
OutputStreamWriter(OutputStream out, final String enc)73     public OutputStreamWriter(OutputStream out, final String enc)
74             throws UnsupportedEncodingException {
75         super(out);
76         if (enc == null) {
77             throw new NullPointerException();
78         }
79         this.out = out;
80         try {
81             encoder = Charset.forName(enc).newEncoder();
82         } catch (Exception e) {
83             throw new UnsupportedEncodingException(enc);
84         }
85         encoder.onMalformedInput(CodingErrorAction.REPLACE);
86         encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
87     }
88 
89     /**
90      * Constructs a new OutputStreamWriter using {@code out} as the target
91      * stream to write converted characters to and {@code cs} as the character
92      * encoding.
93      *
94      * @param out
95      *            the target stream to write converted bytes to.
96      * @param cs
97      *            the {@code Charset} that specifies the character encoding.
98      */
OutputStreamWriter(OutputStream out, Charset cs)99     public OutputStreamWriter(OutputStream out, Charset cs) {
100         super(out);
101         this.out = out;
102         encoder = cs.newEncoder();
103         encoder.onMalformedInput(CodingErrorAction.REPLACE);
104         encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
105     }
106 
107     /**
108      * Constructs a new OutputStreamWriter using {@code out} as the target
109      * stream to write converted characters to and {@code enc} as the character
110      * encoder.
111      *
112      * @param out
113      *            the target stream to write converted bytes to.
114      * @param enc
115      *            the character encoder used for character conversion.
116      */
OutputStreamWriter(OutputStream out, CharsetEncoder enc)117     public OutputStreamWriter(OutputStream out, CharsetEncoder enc) {
118         super(out);
119         enc.charset();
120         this.out = out;
121         encoder = enc;
122     }
123 
124     /**
125      * Closes this writer. This implementation flushes the buffer as well as the
126      * target stream. The target stream is then closed and the resources for the
127      * buffer and converter are released.
128      *
129      * <p>Only the first invocation of this method has any effect. Subsequent calls
130      * do nothing.
131      *
132      * @throws IOException
133      *             if an error occurs while closing this writer.
134      */
135     @Override
close()136     public void close() throws IOException {
137         synchronized (lock) {
138             if (encoder != null) {
139                 drainEncoder();
140                 flushBytes(false);
141                 out.close();
142                 encoder = null;
143                 bytes = null;
144             }
145         }
146     }
147 
148     /**
149      * Flushes this writer. This implementation ensures that all buffered bytes
150      * are written to the target stream. After writing the bytes, the target
151      * stream is flushed as well.
152      *
153      * @throws IOException
154      *             if an error occurs while flushing this writer.
155      */
156     @Override
flush()157     public void flush() throws IOException {
158         flushBytes(true);
159     }
160 
flushBytes(boolean flushUnderlyingStream)161     private void flushBytes(boolean flushUnderlyingStream) throws IOException {
162         synchronized (lock) {
163             checkStatus();
164             int position = bytes.position();
165             if (position > 0) {
166                 bytes.flip();
167                 out.write(bytes.array(), bytes.arrayOffset(), position);
168                 bytes.clear();
169             }
170             if (flushUnderlyingStream) {
171                 out.flush();
172             }
173         }
174     }
175 
convert(CharBuffer chars)176     private void convert(CharBuffer chars) throws IOException {
177         while (true) {
178             CoderResult result = encoder.encode(chars, bytes, false);
179             if (result.isOverflow()) {
180                 // Make room and try again.
181                 flushBytes(false);
182                 continue;
183             } else if (result.isError()) {
184                 result.throwException();
185             }
186             break;
187         }
188     }
189 
drainEncoder()190     private void drainEncoder() throws IOException {
191         // Strictly speaking, I think it's part of the CharsetEncoder contract that you call
192         // encode with endOfInput true before flushing. Our ICU-based implementations don't
193         // actually need this, and you'd hope that any reasonable implementation wouldn't either.
194         // CharsetEncoder.encode doesn't actually pass the boolean through to encodeLoop anyway!
195         CharBuffer chars = CharBuffer.allocate(0);
196         while (true) {
197             CoderResult result = encoder.encode(chars, bytes, true);
198             if (result.isError()) {
199                 result.throwException();
200             } else if (result.isOverflow()) {
201                 flushBytes(false);
202                 continue;
203             }
204             break;
205         }
206 
207         // Some encoders (such as ISO-2022-JP) have stuff to write out after all the
208         // characters (such as shifting back into a default state). In our implementation,
209         // this is actually the first time ICU is told that we've run out of input.
210         CoderResult result = encoder.flush(bytes);
211         while (!result.isUnderflow()) {
212             if (result.isOverflow()) {
213                 flushBytes(false);
214                 result = encoder.flush(bytes);
215             } else {
216                 result.throwException();
217             }
218         }
219     }
220 
checkStatus()221     private void checkStatus() throws IOException {
222         if (encoder == null) {
223             throw new IOException("OutputStreamWriter is closed");
224         }
225     }
226 
227     /**
228      * Returns the historical name of the encoding used by this writer to convert characters to
229      * bytes, or null if this writer has been closed. Most callers should probably keep
230      * track of the String or Charset they passed in; this method may not return the same
231      * name.
232      */
getEncoding()233     public String getEncoding() {
234         if (encoder == null) {
235             return null;
236         }
237         return HistoricalCharsetNames.get(encoder.charset());
238     }
239 
240     /**
241      * Writes {@code count} characters starting at {@code offset} in {@code buf}
242      * to this writer. The characters are immediately converted to bytes by the
243      * character converter and stored in a local buffer. If the buffer gets full
244      * as a result of the conversion, this writer is flushed.
245      *
246      * @param buffer
247      *            the array containing characters to write.
248      * @param offset
249      *            the index of the first character in {@code buf} to write.
250      * @param count
251      *            the maximum number of characters to write.
252      * @throws IndexOutOfBoundsException
253      *             if {@code offset < 0} or {@code count < 0}, or if
254      *             {@code offset + count} is greater than the size of
255      *             {@code buf}.
256      * @throws IOException
257      *             if this writer has already been closed or another I/O error
258      *             occurs.
259      */
260     @Override
write(char[] buffer, int offset, int count)261     public void write(char[] buffer, int offset, int count) throws IOException {
262         synchronized (lock) {
263             checkStatus();
264             Arrays.checkOffsetAndCount(buffer.length, offset, count);
265             CharBuffer chars = CharBuffer.wrap(buffer, offset, count);
266             convert(chars);
267         }
268     }
269 
270     /**
271      * Writes the character {@code oneChar} to this writer. The lowest two bytes
272      * of the integer {@code oneChar} are immediately converted to bytes by the
273      * character converter and stored in a local buffer. If the buffer gets full
274      * by converting this character, this writer is flushed.
275      *
276      * @param oneChar
277      *            the character to write.
278      * @throws IOException
279      *             if this writer is closed or another I/O error occurs.
280      */
281     @Override
write(int oneChar)282     public void write(int oneChar) throws IOException {
283         synchronized (lock) {
284             checkStatus();
285             CharBuffer chars = CharBuffer.wrap(new char[] { (char) oneChar });
286             convert(chars);
287         }
288     }
289 
290     /**
291      * Writes {@code count} characters starting at {@code offset} in {@code str}
292      * to this writer. The characters are immediately converted to bytes by the
293      * character converter and stored in a local buffer. If the buffer gets full
294      * as a result of the conversion, this writer is flushed.
295      *
296      * @param str
297      *            the string containing characters to write.
298      * @param offset
299      *            the start position in {@code str} for retrieving characters.
300      * @param count
301      *            the maximum number of characters to write.
302      * @throws IOException
303      *             if this writer has already been closed or another I/O error
304      *             occurs.
305      * @throws IndexOutOfBoundsException
306      *             if {@code offset < 0} or {@code count < 0}, or if
307      *             {@code offset + count} is bigger than the length of
308      *             {@code str}.
309      */
310     @Override
write(String str, int offset, int count)311     public void write(String str, int offset, int count) throws IOException {
312         synchronized (lock) {
313             if (count < 0) {
314                 throw new StringIndexOutOfBoundsException(str, offset, count);
315             }
316             if (str == null) {
317                 throw new NullPointerException("str == null");
318             }
319             if ((offset | count) < 0 || offset > str.length() - count) {
320                 throw new StringIndexOutOfBoundsException(str, offset, count);
321             }
322             checkStatus();
323             CharBuffer chars = CharBuffer.wrap(str, offset, count + offset);
324             convert(chars);
325         }
326     }
327 
checkError()328     @Override boolean checkError() {
329         return out.checkError();
330     }
331 }
332