1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
88 CharBuffer charBuffer = CharBuffer.allocate(buffer.length);
89
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
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
150
151 in.put(bytes, 0, length);
152
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
159 CoderResult result;
160 do {
161 result = decodeAndWrite(writer, in, out, decoder, endOfInput);
162
163 if (in.hasRemaining()) {
164
165 in.compact();
166 if (result.isOverflow() && !result.isError() && !result.isMalformed()) {
167
168
169 in.flip();
170 }
171 } else {
172
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
181 out.flip();
182
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 }