package org.marketcetera.util.unicode;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import org.marketcetera.util.misc.ClassVersion;
/**
* A variation of {@link OutputStreamWriter} that is BOM-aware. It can
* operate in any of the following modes:
*
* <ul>
*
* <li>As a standard output stream writer that uses the default JVM
* charset.</li>
*
* <li>A writer that uses a specific charset and (optionally) injects
* a specific signature in the header of the output stream.</li>
*
* <li>A writer that uses the signature/charset which a supplied
* reader had used to decode a byte stream. This is useful when a file
* is modified, and the original signature/charset must be
* preserved.</li>
*
* </ul>
*
* @author tlerios@marketcetera.com
* @since 0.6.0
* @version $Id: UnicodeOutputStreamWriter.java 16154 2012-07-14 16:34:05Z colin $
*/
/* $License$ */
@ClassVersion("$Id: UnicodeOutputStreamWriter.java 16154 2012-07-14 16:34:05Z colin $")
public class UnicodeOutputStreamWriter
extends Writer
{
// INSTANCE DATA.
private OutputStream mStream;
private OutputStreamWriter mWriter;
private SignatureCharset mRequestedSignatureCharset;
private SignatureCharset mSignatureCharset;
private boolean mWriteSignature=true;
// CONSTRUCTORS.
/**
* Creates a new writer over the given stream that uses the
* default JVM charset (and no signature is injected).
*
* @param stream The stream.
*/
public UnicodeOutputStreamWriter
(OutputStream stream)
{
super(stream);
mStream=stream;
}
/**
* Creates a new writer over the given stream that normally
* injects the given signature, and uses its associated charset
* for encoding. However, if the charset in the given
* signature/charset pair is not supported by the JVM, the default
* JVM charset is used instead (and no signature is
* injected). Signature injection is also skipped if the given
* flag is set to false (even if the signature/charset pair
* has a signature).
*
* @param stream The stream.
* @param requestedSignatureCharset The signature/charset. It may
* be null to use the default JVM charset.
* @param writeSignature True if a signature (if any) should be
* written.
*/
protected UnicodeOutputStreamWriter
(OutputStream stream,
SignatureCharset requestedSignatureCharset,
boolean writeSignature)
{
this(stream);
mRequestedSignatureCharset=requestedSignatureCharset;
mWriteSignature=writeSignature;
}
/**
* Creates a new writer over the given stream that normally
* injects the given signature, and uses its associated charset
* for encoding. However, if the charset in the given
* signature/charset pair is not supported by the JVM, the default
* JVM charset is used instead (and no signature is injected).
*
* @param stream The stream.
* @param requestedSignatureCharset The signature/charset. It may
* be null to use the default JVM charset.
*/
public UnicodeOutputStreamWriter
(OutputStream stream,
SignatureCharset requestedSignatureCharset)
{
this(stream,requestedSignatureCharset,true);
}
/**
* Creates a new writer over the given stream that uses the actual
* signature/charset of the given reader. If the reader is not a
* valid instance of a {@link UnicodeInputStreamReader}, or if it
* used the default JVM charset, then the writer uses the default
* JVM charset (and no signature is injected). Signature injection
* is also skipped if the given flag is set to false (even if the
* given reader's signature/charset pair has a signature).
*
* @param stream The stream.
* @param reader The reader.
* @param writeSignature True if a signature (if any) should be
* written.
*
* @throws IOException Thrown if an I/O error occurs.
*/
protected UnicodeOutputStreamWriter
(OutputStream stream,
Reader reader,
boolean writeSignature)
throws IOException
{
this(stream);
if (reader instanceof UnicodeInputStreamReader) {
mRequestedSignatureCharset=
((UnicodeInputStreamReader)reader).getSignatureCharset();
mWriteSignature=writeSignature;
}
}
/**
* Creates a new writer over the given stream that uses the actual
* signature/charset of the given reader. If the reader is not a
* valid instance of a {@link UnicodeInputStreamReader}, or if it
* used the default JVM charset, then the writer uses the default
* JVM charset (and no signature is injected).
*
* @param stream The stream.
* @param reader The reader.
*
* @throws IOException Thrown if an I/O error occurs.
*/
public UnicodeOutputStreamWriter
(OutputStream stream,
Reader reader)
throws IOException
{
this(stream,reader,true);
}
// Writer.
@Override
public void write
(int c)
throws IOException
{
synchronized (lock) {
init();
mWriter.write(c);
}
}
@Override
public void write
(char[] cbuf)
throws IOException
{
synchronized (lock) {
init();
mWriter.write(cbuf);
}
}
@Override
public void write
(char cbuf[],
int off,
int len)
throws IOException
{
synchronized (lock) {
init();
mWriter.write(cbuf,off,len);
}
}
@Override
public void write
(String str)
throws IOException
{
synchronized (lock) {
init();
mWriter.write(str);
}
}
@Override
public void write
(String str,
int off,
int len)
throws IOException
{
synchronized (lock) {
init();
mWriter.write(str,off,len);
}
}
@Override
public Writer append
(CharSequence csq)
throws IOException
{
synchronized (lock) {
init();
return mWriter.append(csq);
}
}
@Override
public Writer append
(CharSequence csq,
int start,
int end)
throws IOException
{
synchronized (lock) {
init();
return mWriter.append(csq,start,end);
}
}
@Override
public Writer append
(char c)
throws IOException
{
synchronized (lock) {
init();
return mWriter.append(c);
}
}
@Override
public void flush()
throws IOException
{
synchronized (lock) {
init();
mWriter.flush();
}
}
@Override
public void close()
throws IOException
{
synchronized (lock) {
if (mStream==null) {
return;
}
if (mWriter!=null) {
mWriter.close();
}
mStream.close();
mStream=null;
}
}
// INSTANCE METHODS.
/**
* Returns the receiver's requested signature/charset. This pair
* may have either been requested directly or by requesting that a
* reader's actual signature/charset be matched.
*
* @return The requested signature/charset, which may be null if
* none was requested.
*/
public SignatureCharset getRequestedSignatureCharset()
{
return mRequestedSignatureCharset;
}
/**
* Returns the receiver's actual signature/charset (that is, the
* one in use to encode the stream).
*
* @return The signature/charset, which may be null if the default
* JVM charset is used.
*
* @throws IOException Thrown if an I/O error occurs.
*/
public SignatureCharset getSignatureCharset()
throws IOException
{
synchronized (lock) {
init();
return mSignatureCharset;
}
}
/**
* Initializes the receiver.
*
* @throws IOException Thrown if an I/O error occurs.
*/
private void init()
throws IOException
{
if (mStream==null) {
throw new IOException(Messages.STREAM_CLOSED.getText());
}
if (mWriter!=null) {
return;
}
mSignatureCharset=getRequestedSignatureCharset();
if ((mSignatureCharset!=null) && (!mSignatureCharset.isSupported())) {
mSignatureCharset=null;
}
if (mSignatureCharset!=null) {
if (mWriteSignature) {
mStream.write(mSignatureCharset.getSignature().getMark());
}
mWriter=new OutputStreamWriter
(mStream,mSignatureCharset.getCharset().getCharset());
} else {
mWriter=new OutputStreamWriter(mStream);
}
}
}