/* 
 * 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); 
        } 
    } 
} 
 
    
     
     
     
     
  
  |