/*
* @(#)OutputStreamWriter.java 1.28 00/02/02
*
* Copyright 1990-2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*
*/
package java.io;
import sun.io.CharToByteConverter;
import sun.io.ConversionBufferFullException;
/**
* An OutputStreamWriter is a bridge from character streams to byte streams:
* Characters written to it are translated into bytes according to a specified
* <a href="../lang/package-summary.html#charenc">character encoding</a>. The
* encoding that it uses may be specified by name, or the platform's default
* encoding may be accepted.
*
* <p> Each invocation of a write() method causes the encoding converter to be
* invoked on the given character(s). The resulting bytes are accumulated in a
* buffer before being written to the underlying output stream. The size of
* this buffer may be specified, but by default it is large enough for most
* purposes. Note that the characters passed to the write() methods are not
* buffered.
*
* <p> For top efficiency, consider wrapping an OutputStreamWriter within a
* BufferedWriter so as to avoid frequent converter invocations. For example:
*
* <pre>
* Writer out
* = new BufferedWriter(new OutputStreamWriter(System.out));
* </pre>
*
* <p> A <i>surrogate pair</i> is a character represented by a sequence of two
* <tt>char</tt> values: A <i>high</i> surrogate in the range '\uD800' to
* '\uDBFF' followed by a <i>low</i> surrogate in the range '\uDC00' to
* '\uDFFF'. If the character represented by a surrogate pair cannot be
* encoded by a given encoding then a encoding-dependent <i>substitution
* sequence</i> is written to the output stream.
*
* <p> A <i>malformed surrogate element</i> is a high surrogate that is not
* followed by a low surrogate or a low surrogate that is not preceeded by a
* high surrogate. It is illegal to attempt to write a character stream
* containing malformed surrogate elements. The behavior of an instance of
* this class when a malformed surrogate element is written is not specified.
*
* @see BufferedWriter
* @see OutputStream
* @see <a href="../lang/package-summary.html#charenc">Character encodings</a>
*
* @version 1.28, 02/02/00
* @author Mark Reinhold
* @since JDK1.1
*/
public class OutputStreamWriter extends Writer {
private CharToByteConverter ctb;
private OutputStream out;
private static final int defaultByteBufferSize = 8192;
/* bb is a temporary output buffer into which bytes are written. */
private byte bb[];
/* nextByte is where the next byte will be written into bb */
private int nextByte = 0;
/* nBytes is the buffer size = defaultByteBufferSize in this class */
private int nBytes = 0;
/**
* Create an OutputStreamWriter that uses the named character encoding.
*
* @param out An OutputStream
* @param enc The name of a supported
* <a href="../lang/package-summary.html#charenc">character
* encoding</a>
*
* @exception UnsupportedEncodingException
* If the named encoding is not supported
*/
public OutputStreamWriter(OutputStream out, String enc)
throws UnsupportedEncodingException
{
this(out, CharToByteConverter.getConverter(enc));
}
/**
* Create an OutputStreamWriter that uses the default character encoding.
*
* @param out An OutputStream
*/
public OutputStreamWriter(OutputStream out) {
this(out, CharToByteConverter.getDefault());
}
/**
* Create an OutputStreamWriter that uses the specified character-to-byte
* converter. The converter is assumed to have been reset.
*
* @param out An OutputStream
* @param ctb A CharToByteConverter
*/
private OutputStreamWriter(OutputStream out, CharToByteConverter ctb) {
super(out);
if (out == null)
throw new NullPointerException("out is null");
this.out = out;
this.ctb = ctb;
bb = new byte[defaultByteBufferSize];
nBytes = defaultByteBufferSize;
}
/**
* Returns the canonical name of the character encoding being used by this
* stream.
*
* <p> If this instance was created with the {@link
* #OutputStreamWriter(OutputStream, String)} constructor then the returned
* encoding name, being canonical, may differ from the encoding name passed
* to the constructor. This method may return <code>null</code> if the stream
* has been closed. </p>
*
* <p> NOTE : In J2ME CDC, there is no concept of historical name, so only
* canonical name of character encoding is returned. For a list of
* acceptable canonical names of the character encoding see:
* <a href="http://java.sun.com/j2se/1.4.2/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.4.2/docs/guide/intl/encoding.doc.html</a>
*
* @return a String representing the encoding name, or possibly
* <code>null</code> if the stream has been closed
*
* @see <a href="../lang/package-summary.html#charenc">Character
* encodings</a>
*/
public String getEncoding() {
synchronized (lock) {
if (ctb != null)
return ctb.getCharacterEncoding();
else
return null;
}
}
/** Check to make sure that the stream has not been closed */
private void ensureOpen() throws IOException {
if (out == null)
throw new IOException("Stream closed");
}
/**
* Write a single character.
*
* @exception IOException If an I/O error occurs
*/
public void write(int c) throws IOException {
char cbuf[] = new char[1];
cbuf[0] = (char) c;
write(cbuf, 0, 1);
}
/**
* Write a portion of an array of characters.
*
* @param cbuf Buffer of characters
* @param off Offset from which to start writing characters
* @param len Number of characters to write
*
* @exception IOException If an I/O error occurs
*/
public void write(char cbuf[], int off, int len) throws IOException {
synchronized (lock) {
ensureOpen();
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
((off + len) > cbuf.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
int ci = off, end = off + len;
boolean bufferFlushed = false;
while (ci < end) {
boolean bufferFull = false;
try {
nextByte += ctb.convertAny(cbuf, ci, end,
bb, nextByte, nBytes);
ci = end;
}
catch (ConversionBufferFullException x) {
int nci = ctb.nextCharIndex();
if ((nci == ci) && bufferFlushed) {
/* If the buffer has been flushed and it
still does not hold even one character */
throw new
CharConversionException("Output buffer too small");
}
ci = nci;
bufferFull = true;
nextByte = ctb.nextByteIndex();
}
if ((nextByte >= nBytes) || bufferFull) {
out.write(bb, 0, nextByte);
nextByte = 0;
bufferFlushed = true;
}
}
}
}
/**
* Write a portion of a string.
*
* @param str A String
* @param off Offset from which to start writing characters
* @param len Number of characters to write
*
* @exception IOException If an I/O error occurs
*/
public void write(String str, int off, int len) throws IOException {
/* Check the len before creating a char buffer */
if (len < 0)
throw new IndexOutOfBoundsException();
char cbuf[] = new char[len];
str.getChars(off, off + len, cbuf, 0);
write(cbuf, 0, len);
}
/**
* Flush the output buffer to the underlying byte stream, without flushing
* the byte stream itself. This method is non-private only so that it may
* be invoked by PrintStream.
*/
void flushBuffer() throws IOException {
synchronized (lock) {
ensureOpen();
for (;;) {
try {
nextByte += ctb.flushAny(bb, nextByte, nBytes);
}
catch (ConversionBufferFullException x) {
nextByte = ctb.nextByteIndex();
}
if (nextByte == 0)
break;
if (nextByte > 0) {
out.write(bb, 0, nextByte);
nextByte = 0;
}
}
}
}
/**
* Flush the stream.
*
* @exception IOException If an I/O error occurs
*/
public void flush() throws IOException {
synchronized (lock) {
flushBuffer();
out.flush();
}
}
/**
* Close the stream.
*
* @exception IOException If an I/O error occurs
*/
public void close() throws IOException {
synchronized (lock) {
if (out == null)
return;
flush();
out.close();
out = null;
bb = null;
ctb = null;
}
}
}