Author: mrdon Date: Fri Jan 25 05:31:16 2008 New Revision: 615216 URL: http://svn.apache.org/viewvc?rev=615216&view=rev Log: Fixing problems with fast byte array output stream with large data WW-2412
Modified: struts/struts2/trunk/core/src/main/java/org/apache/struts2/util/FastByteArrayOutputStream.java Modified: struts/struts2/trunk/core/src/main/java/org/apache/struts2/util/FastByteArrayOutputStream.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/java/org/apache/struts2/util/FastByteArrayOutputStream.java?rev=615216&r1=615215&r2=615216&view=diff ============================================================================== --- struts/struts2/trunk/core/src/main/java/org/apache/struts2/util/FastByteArrayOutputStream.java (original) +++ struts/struts2/trunk/core/src/main/java/org/apache/struts2/util/FastByteArrayOutputStream.java Fri Jan 25 05:31:16 2008 @@ -20,12 +20,16 @@ */ package org.apache.struts2.util; -import java.io.IOException; -import java.io.OutputStream; -import java.io.RandomAccessFile; -import java.io.Writer; +import javax.servlet.jsp.JspWriter; +import java.io.*; import java.util.Iterator; import java.util.LinkedList; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.Charset; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.CoderResult; +import java.nio.CharBuffer; +import java.nio.ByteBuffer; /** @@ -35,186 +39,222 @@ * */ 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 LinkedList<byte[]> buffers; + private byte buffer[]; private int index; private int size; + private int blockSize; + private boolean closed; - - // Constructors -------------------------------------------------- public FastByteArrayOutputStream() { this(DEFAULT_BLOCK_SIZE); } - public FastByteArrayOutputStream(int aSize) { - blockSize = aSize; - buffer = new byte[blockSize]; - } - - - public int getSize() { - return size + index; + public FastByteArrayOutputStream(int blockSize) { + buffer = new byte[this.blockSize = blockSize]; } - public void close() { - closed = true; - } - - public byte[] toByteArray() { - byte[] data = new byte[getSize()]; - - // Check if we have a list of buffers - int pos = 0; - + public void writeTo(OutputStream out) throws IOException { if (buffers != null) { - Iterator iter = buffers.iterator(); - - while (iter.hasNext()) { - byte[] bytes = (byte[]) iter.next(); - System.arraycopy(bytes, 0, data, pos, blockSize); - pos += blockSize; + for (byte[] bytes : buffers) { + out.write(bytes, 0, blockSize); } } - - // write the internal buffer directly - System.arraycopy(buffer, 0, data, pos, index); - - return data; - } - - public String toString() { - return new String(toByteArray()); + out.write(buffer, 0, index); } - // OutputStream overrides ---------------------------------------- - public void write(int datum) throws IOException { - if (closed) { - throw new IOException("Stream closed"); - } else { - if (index == blockSize) { - addBuffer(); + public void writeTo(RandomAccessFile out) throws IOException { + if (buffers != null) { + for (byte[] bytes : buffers) { + out.write(bytes, 0, blockSize); } - - // store the byte - buffer[index++] = (byte) datum; } + out.write(buffer, 0, index); } - 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"); + /** + * This is a patched method (added for common Writer, needed for tests) + * @param out Writer + * @param encoding Encoding + * @throws IOException If some output failed + */ + public void writeTo(Writer out, String encoding) throws IOException { + if (encoding != null) { + CharsetDecoder decoder = getDecoder(encoding); + // Create buffer for characters decoding + CharBuffer charBuffer = CharBuffer.allocate(buffer.length); + // Create buffer for bytes + float bytesPerChar = decoder.charset().newEncoder().maxBytesPerChar(); + ByteBuffer byteBuffer = ByteBuffer.allocate((int) (buffer.length + bytesPerChar)); + if (buffers != null) { + for (byte[] bytes : buffers) { + decodeAndWriteOut(out, bytes, bytes.length, byteBuffer, charBuffer, decoder, false); + } + } + decodeAndWriteOut(out, buffer, index, byteBuffer, charBuffer, decoder, true); } 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; + if (buffers != null) { + for (byte[] bytes : buffers) { + writeOut(out, bytes, bytes.length); + } } + writeOut(out, buffer, index); } } - // Public - public void writeTo(OutputStream out) throws IOException { - // Check if we have a list of buffers - if (buffers != null) { - Iterator iter = buffers.iterator(); + private CharsetDecoder getDecoder(String encoding) { + Charset charset = Charset.forName(encoding); + return charset.newDecoder(). + onMalformedInput(CodingErrorAction.REPORT). + onUnmappableCharacter(CodingErrorAction.REPLACE); + } - while (iter.hasNext()) { - byte[] bytes = (byte[]) iter.next(); - out.write(bytes, 0, blockSize); - } + /** + * This is a patched method (standard) + * @param out Writer + * @param encoding Encoding + * @throws IOException If some output failed + */ + public void writeTo(JspWriter out, String encoding) throws IOException { + try { + writeTo((Writer) out, encoding); + } catch (IOException e) { + writeToFile(); + throw e; + } catch (Throwable e) { + writeToFile(); + throw new RuntimeException(e); } - - // 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); - } + /** + * This method is need only for debug. And needed for tests generated files. + */ + private void writeToFile() { + try { + writeTo(new FileOutputStream("/tmp/" + getClass().getName() + System.currentTimeMillis() + ".log")); + } catch (IOException e) { + // Ignore } + } - // write the internal buffer directly - out.write(buffer, 0, index); + private void writeOut(Writer out, byte[] bytes, int length) throws IOException { + out.write(new String(bytes, 0, length)); + } + + private static void decodeAndWriteOut(Writer writer, byte[] bytes, int length, ByteBuffer in, CharBuffer out, CharsetDecoder decoder, boolean endOfInput) throws IOException { + // Append bytes to current buffer + // Previous data maybe partially decoded, this part will appended to previous + in.put(bytes, 0, length); + // To begin of data + in.flip(); + decodeAndWriteBuffered(writer, in, out, decoder, endOfInput); + } + + private static void decodeAndWriteBuffered(Writer writer, ByteBuffer in, CharBuffer out, CharsetDecoder decoder, boolean endOfInput) throws IOException { + // Decode + CoderResult result; + do { + result = decodeAndWrite(writer, in, out, decoder, endOfInput); + // Check that all data are decoded + if (in.hasRemaining()) { + // Move remaining to top of buffer + in.compact(); + if (result.isOverflow() && !result.isError() && !result.isMalformed()) { + // Not all buffer chars decoded, spin it again + // Set to begin + in.flip(); + } + } else { + // Clean up buffer + in.clear(); + } + } while (in.hasRemaining() && result.isOverflow() && !result.isError() && !result.isMalformed()); } - public void writeTo(Writer out, String encoding) throws IOException { - // Check if we have a list of buffers - if (buffers != null) { - Iterator iter = buffers.iterator(); + private static CoderResult decodeAndWrite(Writer writer, ByteBuffer in, CharBuffer out, CharsetDecoder decoder, boolean endOfInput) throws IOException { + CoderResult result = decoder.decode(in, out, endOfInput); + // To begin of decoded data + out.flip(); + // Output + writer.write(out.toString()); + return result; + } - while (iter.hasNext()) { - byte[] bytes = (byte[]) iter.next(); + public int getSize() { + return size + index; + } - if (encoding != null) { - out.write(new String(bytes, encoding)); - } else { - out.write(new String(bytes)); - } + public byte[] toByteArray() { + byte data[] = new byte[getSize()]; + int position = 0; + if (buffers != null) { + for (byte[] bytes : buffers) { + System.arraycopy(bytes, 0, data, position, blockSize); + position += blockSize; } } + System.arraycopy(buffer, 0, data, position, index); + return data; + } - // write the internal buffer directly - if (encoding != null) { - out.write(new String(buffer, 0, index, encoding)); - } else { - out.write(new String(buffer, 0, index)); - } + public String toString() { + return new String(toByteArray()); } - /** - * Create a new buffer and store the - * current one in linked list - */ protected void addBuffer() { if (buffers == null) { - buffers = new LinkedList(); + buffers = new LinkedList<byte[]>(); } - buffers.addLast(buffer); - buffer = new byte[blockSize]; size += index; index = 0; } -} + + public void write(int datum) throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + if (index == blockSize) { + addBuffer(); + } + buffer[index++] = (byte) datum; + } + + public void write(byte data[], int offset, int length) throws IOException { + if (data == null) { + throw new NullPointerException(); + } + if (offset < 0 || offset + length > data.length || length < 0) { + throw new IndexOutOfBoundsException(); + } + if (closed) { + throw new IOException("Stream closed"); + } + if (index + length > blockSize) { + do { + if (index == blockSize) { + addBuffer(); + } + int 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 { + System.arraycopy(data, offset, buffer, index, length); + index += length; + } + } + + public void close() { + closed = true; + } +} \ No newline at end of file