/*
* JBoss, Home of Professional Open Source.
* Copyright 2010, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.process.protocol;
import java.io.IOException;
import java.io.OutputStream;
import org.jboss.marshalling.ByteOutput;
/**
* Byte output implementation that writes the bytes out in chunks. Each invocation of flush will first write a
* {@code CHUNK_START} header followed by the size of the chunk being flushed. Once the closed, this will write out a
* {@code END} byte. This should be used to write data that can be read by a {@link ChunkyByteInput}
* to ensure the reader can not read more than available. This is handy if the consumer of the stream is prone to over-buffering.
*
* @author John Bailey
*/
public class ChunkyByteOutput extends OutputStream implements ByteOutput {
public static final int CHUNK_START = 0x98;
public static final int END = 0x99;
private final ByteOutput output;
private final byte[] buffer;
private int position;
public ChunkyByteOutput(final ByteOutput output) {
this(output, 8192);
}
public ChunkyByteOutput(final ByteOutput output, final int bufferSize) {
this.output = output;
buffer = new byte[bufferSize];
}
/** {@inheritDoc} */
public void write(int v) throws IOException {
final byte[] buffer = this.buffer;
final int position = this.position;
if (position == buffer.length) {
flush();
buffer[0] = (byte) v;
this.position = 1;
} else {
buffer[position] = (byte) v;
this.position = position + 1;
}
}
/** {@inheritDoc} */
public void write(final byte[] bytes) throws IOException {
write(bytes, 0, bytes.length);
}
/** {@inheritDoc} */
public void write(final byte[] bytes, final int off, int len) throws IOException {
byte[] buffer = this.buffer;
final int position = this.position;
int offSet = off;
while (offSet < len) {
int remaining = buffer.length - position;
int toRead = len - offSet;
if (toRead < remaining) {
System.arraycopy(bytes, offSet, buffer, position, toRead);
this.position = position + toRead;
offSet = offSet + len;
} else {
System.arraycopy(bytes, offSet, buffer, position, remaining);
this.position += remaining;
flush();
offSet = offSet + remaining;
}
}
}
/**
* Flushes the current buffer then write a {@code END} byte. <em>This will not close the underlying byte output</em>
*
* @throws IOException
*/
public void close() throws IOException {
flush();
output.write(END); // Don't close the underlying output
}
/**
* Writes a {@code CHUNK_START} header followed by the size of chunk being flushed followed by the data being flushed.
* It will then flush the underlying byte output.
*
* @throws IOException
*/
public void flush() throws IOException {
final ByteOutput output = this.output;
final int pos = this.position;
if (pos > 0) {
output.write(CHUNK_START);
writeInt(pos);
final byte[] buffer = this.buffer;
output.write(buffer, 0, pos);
}
this.position = 0;
}
public void writeInt(final int i) throws IOException {
final ByteOutput output = this.output;
byte[] bytes = new byte[4];
bytes[0] = (byte) (i >> 24);
bytes[1] = (byte) (i >> 16);
bytes[2] = (byte) (i >> 8);
bytes[3] = (byte) i;
output.write(bytes);
}
}