/* See LICENSE for licensing and NOTICE for copyright. */ package org.cryptacular.io; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.ByteBuffer; import java.nio.CharBuffer; import org.cryptacular.codec.Base64Decoder; import org.cryptacular.codec.Decoder; import org.cryptacular.codec.HexDecoder; /** * Filters read bytes through a {@link Decoder} such that consumers obtain raw (decoded) bytes from read operations. * * @author Middleware Services */ public class DecodingInputStream extends FilterInputStream { /** Performs decoding. */ private final Decoder decoder; /** Wraps the input stream to convert bytes to characters. */ private final InputStreamReader reader; /** Holds input bytes as characters. */ private CharBuffer input; /** Receives decoding result. */ private ByteBuffer output; /** * Creates a new instance that wraps the given stream and performs decoding using the given encoder component. * * @param in Input stream to wrap. * @param d Decoder that provides on-the-fly decoding. */ public DecodingInputStream(final InputStream in, final Decoder d) { super(in); if (d == null) { throw new IllegalArgumentException("Decoder cannot be null."); } decoder = d; reader = new InputStreamReader(in); } @Override public int read() throws IOException { return read(new byte[1]); } @Override public int read(final byte[] b) throws IOException { return read(b, 0, b.length); } @Override public int read(final byte[] b, final int off, final int len) throws IOException { prepareInputBuffer(len - off); prepareOutputBuffer(); if (reader.read(input) < 0) { decoder.finalize(output); if (output.position() == 0) { return -1; } } else { input.flip(); decoder.decode(input, output); } output.flip(); output.get(b, off, output.limit()); return output.position(); } /** * Creates a new instance that decodes base64 input from the given stream. * * @param in Wrapped input stream. * * @return Decoding input stream that decodes base64 output. */ public static DecodingInputStream base64(final InputStream in) { return new DecodingInputStream(in, new Base64Decoder()); } /** * Creates a new instance that decodes hexadecimal input from the given stream. * * @param in Wrapped input stream. * * @return Decoding input stream that decodes hexadecimal output. */ public static DecodingInputStream hex(final InputStream in) { return new DecodingInputStream(in, new HexDecoder()); } /** * Prepares the input buffer to receive the given number of bytes. * * @param required Number of bytes required. */ private void prepareInputBuffer(final int required) { if (input == null || input.capacity() < required) { input = CharBuffer.allocate(required); } else { input.clear(); } } /** Prepares the output buffer based on input buffer capacity. */ private void prepareOutputBuffer() { final int required = decoder.outputSize(input.capacity()); if (output == null || output.capacity() < required) { output = ByteBuffer.allocate(required); } else { output.clear(); } } }