/*
* Copyright (c) 2002-2003 by OpenSymphony
* All rights reserved.
*/
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.io.Writer;
import java.util.Iterator;
import java.util.LinkedList;
/**
* A speedy implementation of ByteArrayOutputStream. It's not synchronized, and it
* does not copy buffers when it's expanded. There's also no copying of the internal buffer
* if it's contents is extracted with the writeTo(stream) method.
*
* @author Rickard ?berg
* @author Brat Baker (Atlassian)
* @author Alexey
* @version $Date: 2008-01-19 10:09:56 +0800 (Sat, 19 Jan 2008) $ $Id: FastByteArrayOutputStream.java 3000 2008-01-19 02:09:56Z tm_jee $
*/
public class FastByteArrayOutputStream extends OutputStream {
// Static --------------------------------------------------------
private static final int DEFAULT_BLOCK_SIZE = 8192;
private LinkedList buffers;
// Attributes ----------------------------------------------------
// internal buffer
private byte[] buffer;
// is the stream closed?
private boolean closed;
private int blockSize;
private int index;
private int size;
// Constructors --------------------------------------------------
public FastByteArrayOutputStream() {
this(DEFAULT_BLOCK_SIZE);
}
public FastByteArrayOutputStream(int aSize) {
blockSize = aSize;
buffer = new byte[blockSize];
}
public int getSize() {
return size + index;
}
public void close() {
closed = true;
}
public byte[] toByteArray() {
byte[] data = new byte[getSize()];
// Check if we have a list of buffers
int pos = 0;
if (buffers != null) {
Iterator iter = buffers.iterator();
while (iter.hasNext()) {
byte[] bytes = (byte[]) iter.next();
System.arraycopy(bytes, 0, data, pos, blockSize);
pos += blockSize;
}
}
// write the internal buffer directly
System.arraycopy(buffer, 0, data, pos, index);
return data;
}
public String toString() {
return new String(toByteArray());
}
// OutputStream overrides ----------------------------------------
public void write(int datum) throws IOException {
if (closed) {
throw new IOException("Stream closed");
} else {
if (index == blockSize) {
addBuffer();
}
// store the byte
buffer[index++] = (byte) datum;
}
}
public void write(byte[] data, int offset, int length) throws IOException {
if (data == null) {
throw new NullPointerException();
} else if ((offset < 0) || ((offset + length) > data.length) || (length < 0)) {
throw new IndexOutOfBoundsException();
} else if (closed) {
throw new IOException("Stream closed");
} else {
if ((index + length) > blockSize) {
int copyLength;
do {
if (index == blockSize) {
addBuffer();
}
copyLength = blockSize - index;
if (length < copyLength) {
copyLength = length;
}
System.arraycopy(data, offset, buffer, index, copyLength);
offset += copyLength;
index += copyLength;
length -= copyLength;
} while (length > 0);
} else {
// Copy in the subarray
System.arraycopy(data, offset, buffer, index, length);
index += length;
}
}
}
// Public
public void writeTo(OutputStream out) throws IOException {
// Check if we have a list of buffers
if (buffers != null) {
Iterator iter = buffers.iterator();
while (iter.hasNext()) {
byte[] bytes = (byte[]) iter.next();
out.write(bytes, 0, blockSize);
}
}
// write the internal buffer directly
out.write(buffer, 0, index);
}
public void writeTo(RandomAccessFile out) throws IOException {
// Check if we have a list of buffers
if (buffers != null) {
Iterator iter = buffers.iterator();
while (iter.hasNext()) {
byte[] bytes = (byte[]) iter.next();
out.write(bytes, 0, blockSize);
}
}
// write the internal buffer directly
out.write(buffer, 0, index);
}
public void writeTo(Writer out, String encoding) throws IOException {
/*
There is design tradeoff between being fast, correct and using too much memory when decoding bytes to strings.
The rules are thus :
1. if there is only one buffer then its a simple String conversion
REASON : Fast!!!
2. uses full buffer allocation annd System.arrayCopy() to smooosh together the bytes
and then use String conversion
REASON : Fast at the expense of a known amount of memory (eg the used memory * 2)
*/
if (buffers != null)
{
// RULE 2 : a balance between using some memory and speed
writeToViaSmoosh(out, encoding);
}
else
{
// RULE 1 : fastest!
writeToViaString(out, encoding);
}
}
/**
* This can <b>ONLY</b> be called if there is only a single buffer to write, instead
* use {@link #writeTo(java.io.Writer, String)}, which auto detects if
* {@link #writeToViaString(java.io.Writer, String)} is to be used or
* {@link #writeToViaSmoosh(java.io.Writer, String)}.
*
* @param out the JspWriter
* @param encoding the encoding
* @throws IOException
*/
void writeToViaString(Writer out, String encoding) throws IOException
{
byte[] bufferToWrite = buffer; // this is always the last buffer to write
int bufferToWriteLen = index; // index points to our place in the last buffer
writeToImpl(out, encoding, bufferToWrite, bufferToWriteLen);
}
/**
* This is recommended to be used where there's more than 1 buffer to write, instead
* use {@link #writeTo(java.io.Writer, String)} which auto detects if
* {@link #writeToViaString(java.io.Writer, String)} is to be used or
* {@link #writeToViaSmoosh(java.io.Writer, String)}.
*
* @param out
* @param encoding
* @throws IOException
*/
void writeToViaSmoosh(Writer out, String encoding) throws IOException
{
byte[] bufferToWrite = toByteArray();
int bufferToWriteLen = bufferToWrite.length;
writeToImpl(out, encoding, bufferToWrite, bufferToWriteLen);
}
/**
* Write <code>bufferToWriteLen</code> of bytes from <code>bufferToWrite</code> to
* <code>out</code> encoding it at the same time.
*
* @param out
* @param encoding
* @param bufferToWrite
* @param bufferToWriteLen
* @throws IOException
*/
private void writeToImpl(Writer out, String encoding, byte[] bufferToWrite, int bufferToWriteLen)
throws IOException
{
String writeStr;
if (encoding != null)
{
writeStr = new String(bufferToWrite, 0, bufferToWriteLen, encoding);
}
else
{
writeStr = new String(bufferToWrite, 0, bufferToWriteLen);
}
out.write(writeStr);
}
/**
* Create a new buffer and store the
* current one in linked list
*/
protected void addBuffer() {
if (buffers == null) {
buffers = new LinkedList();
}
buffers.addLast(buffer);
buffer = new byte[blockSize];
size += index;
index = 0;
}
}
////////////////////////////
import java.io.IOException;
import javax.servlet.jsp.JspWriter;
/**
* A test class for {@link webwork.util.FastByteArrayOutputStream}
*
* @author Brad Baker (Atlassian)
* @since $Date$ $Id$
*/
public class FastByteArrayOutputStreamTestCase extends AbstractEncodingTestCase
{
public void testLatinCharsets() throws Exception
{
assertEncoding(ASCII_TEXT, LATIN);
assertEncoding(ASCII_TEXT, ASCII);
}
public void testRussianCharsets() throws Exception
{
assertEncoding(RUSSIAN_DESC_SHORT, KOI8_R);
assertEncoding(RUSSIAN_DESC1, KOI8_R);
assertEncoding(RUSSIAN_DESC_SHORT, WINDOWS_CYRILLIC);
assertEncoding(RUSSIAN_DESC1, WINDOWS_CYRILLIC);
}
public void testUnicodeCharsets() throws Exception
{
String[] testStrs = {ASCII_TEXT_SHORT, ASCII_TEXT, RUSSIAN_DESC_SHORT, RUSSIAN_DESC1, CHINESE_TIMETRACKING, HUNGRIAN_APPLET_PROBLEM, };
String[] encodings = { UTF_8, UTF_16, UBIG_ENDIAN, ULITTLE_ENDIAN, UBIG_ENDIAN_UNMARKED, ULITTLE_ENDIAN_UNMARKED };
assertEncodings(testStrs, encodings);
}
protected void implementEncodingTest(final String srcStr, final String encoding, final int bufferSize)
throws Exception
{
FastByteArrayOutputStream bout = new FastByteArrayOutputStream(bufferSize);
byte[] bytes = srcStr.getBytes(encoding);
bout.write(bytes);
JspWriter writer = new StringCapturingJspWriter();
bout.writeTo(writer, encoding);
String actualStr = writer.toString();
String expectedStr = new String(bytes, encoding);
assertTheyAreEqual(expectedStr, actualStr, encoding);
}
/**
* Before it was changed to use {@link java.nio.charset.CharsetDecoder} is took an this time
* <p/>
* Total Call Time = 1112.0ms
* Average Call Time = 0.001112ms
* <p/>
* Now with the change it takes this time
* <p/>
* <p/>
* The idea is that it did not get significantly worse in performance
*
* @throws IOException
*/
public void testPerformanceOfWriteToJspWriter() throws IOException
{
final String NINEK_STR = makeRoughly(ASCII, 9 * K);
final String FIFTYK_STR = makeRoughly(ASCII, 50 * K);
final String ONEHUNDREDK_STR = makeRoughly(ASCII, 100 * K);
testPerformanceOfWriteToJspWriter(new TextSource()
{
public String getDesc()
{
return "With < than 8K of data";
}
public String getText(final int times)
{
return ASCII_TEXT;
}
});
testPerformanceOfWriteToJspWriter(new TextSource()
{
public String getDesc()
{
return "With > than 8K of data";
}
public String getText(final int times)
{
return NINEK_STR;
}
});
testPerformanceOfWriteToJspWriter(new TextSource()
{
public String getDesc()
{
return "With a 2/3 mix of small data and 1/3 > 8K of data";
}
public String getText(final int times)
{
if (times % 3 == 0)
{
return NINEK_STR;
}
return ASCII_TEXT;
}
});
testPerformanceOfWriteToJspWriter(new TextSource()
{
public String getDesc()
{
return "With a 1/2 mix of small data and 1/2 > 8K of data";
}
public String getText(final int times)
{
if (times % 2 == 0)
{
return NINEK_STR;
}
return ASCII_TEXT;
}
});
testPerformanceOfWriteToJspWriter(new TextSource()
{
public String getDesc()
{
return "With 50K of data";
}
public String getText(final int times)
{
return FIFTYK_STR;
}
});
testPerformanceOfWriteToJspWriter(new TextSource()
{
public String getDesc()
{
return "With 100K of data";
}
public String getText(final int times)
{
return ONEHUNDREDK_STR;
}
});
}
public void testPerformanceOfWriteToJspWriter(TextSource textSource) throws IOException
{
NoopJspWriter noopJspWriter = new NoopJspWriter();
String[] methods = {
"writeTo (using hueristics)",
"writeToViaSmoosh",
};
System.out.println(textSource.getDesc());
System.out.println();
float bestTime = Float.MAX_VALUE;
String bestMethod = methods[0];
for (int methodIndex = 0; methodIndex < methods.length; methodIndex++)
{
String method = methods[methodIndex];
float totalTime = 0;
final int MAX_TIMES = 10;
final int MAX_ITERATIONS = 100;
for (int times = 0; times < MAX_TIMES; times++)
{
String srcText = textSource.getText(times);
for (int i = 0; i < MAX_ITERATIONS; i++)
{
FastByteArrayOutputStream bout = new FastByteArrayOutputStream();
bout.write(srcText.getBytes(UTF_8));
// just time the JspWriter output. And let it warm u first as well
if (times > 3)
{
long then = System.currentTimeMillis();
switch (methodIndex)
{
case 0:
bout.writeTo(noopJspWriter, UTF_8);
break;
case 1:
bout.writeToViaSmoosh(noopJspWriter, UTF_8);
break;
}
long now = System.currentTimeMillis();
totalTime += (now - then);
}
}
}
float avgTime = totalTime / MAX_TIMES / MAX_ITERATIONS;
System.out.println(method + " - Total Call Time = " + totalTime + "ms");
System.out.println(method + " - Average Call Time = " + avgTime + "ms");
System.out.println();
if (avgTime < bestTime) {
bestTime = avgTime;
bestMethod = method;
}
}
System.out.println(bestMethod + " was the best method - Average Call Time = " + bestTime + "ms");
System.out.println("____________________\n");
}
interface TextSource
{
String getDesc();
String getText(int times);
}
static class StringCapturingJspWriter extends NoopJspWriter
{
StringCapturingJspWriter()
{
super(true);
}
}
static class NoopJspWriter extends JspWriter
{
final StringBuffer sb = new StringBuffer();
final boolean capture;
NoopJspWriter()
{
this(false);
}
NoopJspWriter(boolean capture)
{
super(0, false);
this.capture = capture;
}
NoopJspWriter(final int i, final boolean b)
{
super(i, b);
this.capture = false;
}
public String toString()
{
return sb.toString();
}
public void clear() throws IOException
{
}
public void clearBuffer() throws IOException
{
}
public void close() throws IOException
{
}
public void flush() throws IOException
{
}
public int getRemaining()
{
return 0;
}
public void newLine() throws IOException
{
}
public void print(final char c) throws IOException
{
if (capture)
{
sb.append(c);
}
}
public void print(final double v) throws IOException
{
if (capture)
{
sb.append(v);
}
}
public void print(final float v) throws IOException
{
if (capture)
{
sb.append(v);
}
}
public void print(final int i) throws IOException
{
if (capture)
{
sb.append(i);
}
}
public void print(final long l) throws IOException
{
if (capture)
{
sb.append(l);
}
}
public void print(final Object o) throws IOException
{
if (capture)
{
sb.append(o);
}
}
public void print(final String s) throws IOException
{
if (capture)
{
sb.append(s);
}
}
public void print(final boolean b) throws IOException
{
if (capture)
{
sb.append(b);
}
}
public void print(final char[] chars) throws IOException
{
if (capture)
{
sb.append(chars);
}
}
public void println() throws IOException
{
print('\n');
}
public void println(final char c) throws IOException
{
print(c);
println();
}
public void println(final double v) throws IOException
{
print(v);
println();
}
public void println(final float v) throws IOException
{
print(v);
println();
}
public void println(final int i) throws IOException
{
print(i);
println();
}
public void println(final long l) throws IOException
{
print(l);
println();
}
public void println(final Object o) throws IOException
{
print(o);
println();
}
public void println(final String s) throws IOException
{
print(s);
println();
}
public void println(final boolean b) throws IOException
{
print(b);
println();
}
public void println(final char[] chars) throws IOException
{
print(chars);
println();
}
public void write(final char cbuf[], final int off, final int len) throws IOException
{
String s = new String(cbuf, off, len);
print(s);
}
}
}