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 FileOutputStream fileOutputStream = null;
138 try {
139 fileOutputStream = new FileOutputStream("/tmp/" + getClass().getName() + System.currentTimeMillis() + ".log");
140 writeTo(fileOutputStream);
141 } catch (IOException e) {
142
143 } finally {
144 if (fileOutputStream != null) {
145 try {
146 fileOutputStream.close();
147 } catch (IOException e) {
148
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
160
161 in.put(bytes, 0, length);
162
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
169 CoderResult result;
170 do {
171 result = decodeAndWrite(writer, in, out, decoder, endOfInput);
172
173 if (in.hasRemaining()) {
174
175 in.compact();
176 if (result.isOverflow() && !result.isError() && !result.isMalformed()) {
177
178
179 in.flip();
180 }
181 } else {
182
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
191 out.flip();
192
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 }