/* See LICENSE for licensing and NOTICE for copyright. */ package org.cryptacular.io; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.ByteBuffer; import java.nio.CharBuffer; import org.cryptacular.codec.Base64Encoder; import org.cryptacular.codec.Encoder; import org.cryptacular.codec.HexEncoder; /** * Filters written bytes through an {@link Encoder} such that encoded data is written to the underlying output stream. * * @author Middleware Services */ public class EncodingOutputStream extends FilterOutputStream { /** Performs decoding. */ private final Encoder encoder; /** Wraps the output stream to convert characters to bytes. */ private final OutputStreamWriter writer; /** Receives encoding result. */ private CharBuffer output; /** * Creates a new instance that wraps the given stream and performs encoding using the given encoder component. * * @param out Output stream to wrap. * @param e Encoder that provides on-the-fly encoding. */ public EncodingOutputStream(final OutputStream out, final Encoder e) { super(out); if (e == null) { throw new IllegalArgumentException("Encoder cannot be null."); } encoder = e; writer = new OutputStreamWriter(out); } @Override public void write(final int b) throws IOException { write(new byte[] {(byte) b}); } @Override public void write(final byte[] b) throws IOException { write(b, 0, b.length); } @Override public void write(final byte[] b, final int off, final int len) throws IOException { final ByteBuffer input = ByteBuffer.wrap(b, off, len); final int required = encoder.outputSize(len - off); if (output == null || output.capacity() < required) { output = CharBuffer.allocate(required); } else { output.clear(); } encoder.encode(input, output); output.flip(); writer.write(output.toString()); writer.flush(); } @Override public void flush() throws IOException { writer.flush(); } @Override public void close() throws IOException { if (output == null) { output = CharBuffer.allocate(8); } else { output.clear(); } encoder.finalize(output); output.flip(); writer.write(output.toString()); writer.flush(); writer.close(); } /** * Creates a new instance that produces base64 output in the given stream. * * <p><strong>NOTE:</strong> there are no line breaks in the output with this version.</p> * * @param out Wrapped output stream. * * @return Encoding output stream that produces base64 output. */ public static EncodingOutputStream base64(final OutputStream out) { return base64(out, -1); } /** * Creates a new instance that produces base64 output in the given stream. * * <p><strong>NOTE:</strong> this version supports output with configurable line breaks.</p> * * @param out Wrapped output stream. * @param lineLength Length of each base64-encoded line in output. A zero or negative value disables line breaks. * * @return Encoding output stream that produces base64 output. */ public static EncodingOutputStream base64(final OutputStream out, final int lineLength) { return new EncodingOutputStream(out, new Base64Encoder(lineLength)); } /** * Creates a new instance that produces hexadecimal output in the given stream. * * <p><strong>NOTE:</strong> there are no line breaks in the output.</p> * * @param out Wrapped output stream. * * @return Encoding output stream that produces hexadecimal output. */ public static EncodingOutputStream hex(final OutputStream out) { return new EncodingOutputStream(out, new HexEncoder()); } }