View Javadoc

1   /*
2    * $Id: FastByteArrayOutputStream.java 768855 2009-04-27 02:09:35Z wesw $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  package org.apache.struts2.util;
23  
24  import javax.servlet.jsp.JspWriter;
25  import java.io.*;
26  import java.util.Iterator;
27  import java.util.LinkedList;
28  import java.nio.charset.CharsetDecoder;
29  import java.nio.charset.Charset;
30  import java.nio.charset.CodingErrorAction;
31  import java.nio.charset.CoderResult;
32  import java.nio.CharBuffer;
33  import java.nio.ByteBuffer;
34  
35  
36  /***
37   * A speedy implementation of ByteArrayOutputStream. It's not synchronized, and it
38   * does not copy buffers when it's expanded. There's also no copying of the internal buffer
39   * if it's contents is extracted with the writeTo(stream) method.
40   *
41   */
42  public class FastByteArrayOutputStream extends OutputStream {
43      private static final int DEFAULT_BLOCK_SIZE = 8192;
44  
45      private LinkedList<byte[]> buffers;
46      private byte buffer[];
47      private int index;
48      private int size;
49      private int blockSize;
50      private boolean closed;
51  
52      public FastByteArrayOutputStream() {
53          this(DEFAULT_BLOCK_SIZE);
54      }
55  
56      public FastByteArrayOutputStream(int blockSize) {
57          buffer = new byte[this.blockSize = blockSize];
58      }
59  
60      public void writeTo(OutputStream out) throws IOException {
61          if (buffers != null) {
62              for (byte[] bytes : buffers) {
63                  out.write(bytes, 0, blockSize);
64              }
65          }
66          out.write(buffer, 0, index);
67      }
68  
69      public void writeTo(RandomAccessFile out) throws IOException {
70          if (buffers != null) {
71              for (byte[] bytes : buffers) {
72                  out.write(bytes, 0, blockSize);
73              }
74          }
75          out.write(buffer, 0, index);
76      }
77  
78      /***
79       * This is a patched method (added for common Writer, needed for tests)
80       * @param out Writer
81       * @param encoding Encoding
82       * @throws IOException If some output failed
83       */
84      public void writeTo(Writer out, String encoding) throws IOException {
85          if (encoding != null) {
86              CharsetDecoder decoder = getDecoder(encoding);
87              // Create buffer for characters decoding
88              CharBuffer charBuffer = CharBuffer.allocate(buffer.length);
89              // Create buffer for bytes
90              float bytesPerChar = decoder.charset().newEncoder().maxBytesPerChar();
91              ByteBuffer byteBuffer = ByteBuffer.allocate((int) (buffer.length + bytesPerChar));
92              if (buffers != null) {
93                  for (byte[] bytes : buffers) {
94                      decodeAndWriteOut(out, bytes, bytes.length, byteBuffer, charBuffer, decoder, false);
95                  }
96              }
97              decodeAndWriteOut(out, buffer, index, byteBuffer, charBuffer, decoder, true);
98          } else {
99              if (buffers != null) {
100                 for (byte[] bytes : buffers) {
101                     writeOut(out, bytes, bytes.length);
102                 }
103             }
104             writeOut(out, buffer, index);
105         }
106     }
107 
108     private CharsetDecoder getDecoder(String encoding) {
109         Charset charset = Charset.forName(encoding);
110         return charset.newDecoder().
111                 onMalformedInput(CodingErrorAction.REPORT).
112                 onUnmappableCharacter(CodingErrorAction.REPLACE);
113     }
114 
115     /***
116      * This is a patched method (standard)
117      * @param out Writer
118      * @param encoding Encoding
119      * @throws IOException If some output failed
120      */
121     public void writeTo(JspWriter out, String encoding) throws IOException {
122         try {
123             writeTo((Writer) out, encoding);
124         } catch (IOException e) {
125             writeToFile();
126             throw e;
127         } catch (Throwable e) {
128             writeToFile();
129             throw new RuntimeException(e);
130         }
131     }
132 
133     /***
134      * This method is need only for debug. And needed for tests generated files.
135      */
136     private void writeToFile() {
137         FileOutputStream fileOutputStream = null;
138         try {
139             fileOutputStream = new FileOutputStream("/tmp/" + getClass().getName() + System.currentTimeMillis() + ".log");
140             writeTo(fileOutputStream);
141         } catch (IOException e) {
142             // Ignore
143         } finally {
144             if (fileOutputStream != null) {
145                 try {
146                     fileOutputStream.close();
147                 } catch (IOException e) {
148                     // Ignore
149                 }
150             }
151         }
152     }
153 
154     private void writeOut(Writer out, byte[] bytes, int length) throws IOException {
155         out.write(new String(bytes, 0, length));
156     }
157 
158     private static void decodeAndWriteOut(Writer writer, byte[] bytes, int length, ByteBuffer in, CharBuffer out, CharsetDecoder decoder, boolean endOfInput) throws IOException {
159         // Append bytes to current buffer
160         // Previous data maybe partially decoded, this part will appended to previous
161         in.put(bytes, 0, length);
162         // To begin of data
163         in.flip();
164         decodeAndWriteBuffered(writer, in, out, decoder, endOfInput);
165     }
166 
167     private static void decodeAndWriteBuffered(Writer writer, ByteBuffer in, CharBuffer out, CharsetDecoder decoder, boolean endOfInput) throws IOException {
168         // Decode
169         CoderResult result;
170         do {
171             result = decodeAndWrite(writer, in, out, decoder, endOfInput);
172             // Check that all data are decoded
173             if (in.hasRemaining()) {
174                 // Move remaining to top of buffer
175                 in.compact();
176                 if (result.isOverflow() && !result.isError() && !result.isMalformed()) {
177                     // Not all buffer chars decoded, spin it again
178                     // Set to begin
179                     in.flip();
180                 }
181             } else {
182                 // Clean up buffer
183                 in.clear();
184             }
185         } while (in.hasRemaining() && result.isOverflow() && !result.isError() && !result.isMalformed());
186     }
187 
188     private static CoderResult decodeAndWrite(Writer writer, ByteBuffer in, CharBuffer out, CharsetDecoder decoder, boolean endOfInput) throws IOException {
189         CoderResult result = decoder.decode(in, out, endOfInput);
190         // To begin of decoded data
191         out.flip();
192         // Output
193         writer.write(out.toString());
194         return result;
195     }
196 
197     public int getSize() {
198         return size + index;
199     }
200 
201     public byte[] toByteArray() {
202         byte data[] = new byte[getSize()];
203         int position = 0;
204         if (buffers != null) {
205             for (byte[] bytes : buffers) {
206                 System.arraycopy(bytes, 0, data, position, blockSize);
207                 position += blockSize;
208             }
209         }
210         System.arraycopy(buffer, 0, data, position, index);
211         return data;
212     }
213 
214     public String toString() {
215         return new String(toByteArray());
216     }
217 
218     protected void addBuffer() {
219         if (buffers == null) {
220             buffers = new LinkedList<byte[]>();
221         }
222         buffers.addLast(buffer);
223         buffer = new byte[blockSize];
224         size += index;
225         index = 0;
226     }
227 
228     public void write(int datum) throws IOException {
229         if (closed) {
230             throw new IOException("Stream closed");
231         }
232         if (index == blockSize) {
233             addBuffer();
234         }
235         buffer[index++] = (byte) datum;
236     }
237 
238     public void write(byte data[], int offset, int length) throws IOException {
239         if (data == null) {
240             throw new NullPointerException();
241         }
242         if (offset < 0 || offset + length > data.length || length < 0) {
243             throw new IndexOutOfBoundsException();
244         }
245         if (closed) {
246             throw new IOException("Stream closed");
247         }
248         if (index + length > blockSize) {
249             do {
250                 if (index == blockSize) {
251                     addBuffer();
252                 }
253                 int copyLength = blockSize - index;
254                 if (length < copyLength) {
255                     copyLength = length;
256                 }
257                 System.arraycopy(data, offset, buffer, index, copyLength);
258                 offset += copyLength;
259                 index += copyLength;
260                 length -= copyLength;
261             } while (length > 0);
262         } else {
263             System.arraycopy(data, offset, buffer, index, length);
264             index += length;
265         }
266     }
267 
268     public void close() {
269         closed = true;
270     }
271 }