View Javadoc

1   /*
2    * $Id: FastByteArrayOutputStream.java 651946 2008-04-27 13:41:38Z apetrelli $
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         try {
138             writeTo(new FileOutputStream("/tmp/" + getClass().getName() + System.currentTimeMillis() + ".log"));
139         } catch (IOException e) {
140             // Ignore
141         }
142     }
143 
144     private void writeOut(Writer out, byte[] bytes, int length) throws IOException {
145         out.write(new String(bytes, 0, length));
146     }
147 
148     private static void decodeAndWriteOut(Writer writer, byte[] bytes, int length, ByteBuffer in, CharBuffer out, CharsetDecoder decoder, boolean endOfInput) throws IOException {
149         // Append bytes to current buffer
150         // Previous data maybe partially decoded, this part will appended to previous
151         in.put(bytes, 0, length);
152         // To begin of data
153         in.flip();
154         decodeAndWriteBuffered(writer, in, out, decoder, endOfInput);
155     }
156 
157     private static void decodeAndWriteBuffered(Writer writer, ByteBuffer in, CharBuffer out, CharsetDecoder decoder, boolean endOfInput) throws IOException {
158         // Decode
159         CoderResult result;
160         do {
161             result = decodeAndWrite(writer, in, out, decoder, endOfInput);
162             // Check that all data are decoded
163             if (in.hasRemaining()) {
164                 // Move remaining to top of buffer
165                 in.compact();
166                 if (result.isOverflow() && !result.isError() && !result.isMalformed()) {
167                     // Not all buffer chars decoded, spin it again
168                     // Set to begin
169                     in.flip();
170                 }
171             } else {
172                 // Clean up buffer
173                 in.clear();
174             }
175         } while (in.hasRemaining() && result.isOverflow() && !result.isError() && !result.isMalformed());
176     }
177 
178     private static CoderResult decodeAndWrite(Writer writer, ByteBuffer in, CharBuffer out, CharsetDecoder decoder, boolean endOfInput) throws IOException {
179         CoderResult result = decoder.decode(in, out, endOfInput);
180         // To begin of decoded data
181         out.flip();
182         // Output
183         writer.write(out.toString());
184         return result;
185     }
186 
187     public int getSize() {
188         return size + index;
189     }
190 
191     public byte[] toByteArray() {
192         byte data[] = new byte[getSize()];
193         int position = 0;
194         if (buffers != null) {
195             for (byte[] bytes : buffers) {
196                 System.arraycopy(bytes, 0, data, position, blockSize);
197                 position += blockSize;
198             }
199         }
200         System.arraycopy(buffer, 0, data, position, index);
201         return data;
202     }
203 
204     public String toString() {
205         return new String(toByteArray());
206     }
207 
208     protected void addBuffer() {
209         if (buffers == null) {
210             buffers = new LinkedList<byte[]>();
211         }
212         buffers.addLast(buffer);
213         buffer = new byte[blockSize];
214         size += index;
215         index = 0;
216     }
217 
218     public void write(int datum) throws IOException {
219         if (closed) {
220             throw new IOException("Stream closed");
221         }
222         if (index == blockSize) {
223             addBuffer();
224         }
225         buffer[index++] = (byte) datum;
226     }
227 
228     public void write(byte data[], int offset, int length) throws IOException {
229         if (data == null) {
230             throw new NullPointerException();
231         }
232         if (offset < 0 || offset + length > data.length || length < 0) {
233             throw new IndexOutOfBoundsException();
234         }
235         if (closed) {
236             throw new IOException("Stream closed");
237         }
238         if (index + length > blockSize) {
239             do {
240                 if (index == blockSize) {
241                     addBuffer();
242                 }
243                 int copyLength = blockSize - index;
244                 if (length < copyLength) {
245                     copyLength = length;
246                 }
247                 System.arraycopy(data, offset, buffer, index, copyLength);
248                 offset += copyLength;
249                 index += copyLength;
250                 length -= copyLength;
251             } while (length > 0);
252         } else {
253             System.arraycopy(data, offset, buffer, index, length);
254             index += length;
255         }
256     }
257 
258     public void close() {
259         closed = true;
260     }
261 }