1 | package org.apache.velocity.io; |
2 | |
3 | /* |
4 | * Copyright 2000-2001,2004 The Apache Software Foundation. |
5 | * |
6 | * Licensed under the Apache License, Version 2.0 (the "License"); |
7 | * you may not use this file except in compliance with the License. |
8 | * You may obtain a copy of the License at |
9 | * |
10 | * http://www.apache.org/licenses/LICENSE-2.0 |
11 | * |
12 | * Unless required by applicable law or agreed to in writing, software |
13 | * distributed under the License is distributed on an "AS IS" BASIS, |
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
15 | * See the License for the specific language governing permissions and |
16 | * limitations under the License. |
17 | */ |
18 | |
19 | import java.io.IOException; |
20 | import java.io.Writer; |
21 | |
22 | /** |
23 | * Implementation of a fast Writer. It was originally taken from JspWriter |
24 | * and modified to have less syncronization going on. |
25 | * |
26 | * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> |
27 | * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a> |
28 | * @author Anil K. Vijendran |
29 | * @version $Id: VelocityWriter.java,v 1.8.4.1 2004/03/03 23:22:54 geirm Exp $ |
30 | */ |
31 | public final class VelocityWriter extends Writer |
32 | { |
33 | /** |
34 | * constant indicating that the Writer is not buffering output |
35 | */ |
36 | public static final int NO_BUFFER = 0; |
37 | |
38 | /** |
39 | * constant indicating that the Writer is buffered and is using the |
40 | * implementation default buffer size |
41 | */ |
42 | public static final int DEFAULT_BUFFER = -1; |
43 | |
44 | /** |
45 | * constant indicating that the Writer is buffered and is unbounded; |
46 | * this is used in BodyContent |
47 | */ |
48 | public static final int UNBOUNDED_BUFFER = -2; |
49 | |
50 | protected int bufferSize; |
51 | protected boolean autoFlush; |
52 | |
53 | private Writer writer; |
54 | |
55 | private char cb[]; |
56 | private int nextChar; |
57 | |
58 | private static int defaultCharBufferSize = 8 * 1024; |
59 | |
60 | private boolean flushed = false; |
61 | |
62 | /** |
63 | * Create a buffered character-output stream that uses a default-sized |
64 | * output buffer. |
65 | * |
66 | * @param response A Servlet Response |
67 | */ |
68 | public VelocityWriter(Writer writer) |
69 | { |
70 | this(writer, defaultCharBufferSize, true); |
71 | } |
72 | |
73 | /** |
74 | * private constructor. |
75 | */ |
76 | private VelocityWriter(int bufferSize, boolean autoFlush) |
77 | { |
78 | this.bufferSize = bufferSize; |
79 | this.autoFlush = autoFlush; |
80 | } |
81 | |
82 | /** |
83 | * This method returns the size of the buffer used by the JspWriter. |
84 | * |
85 | * @return the size of the buffer in bytes, or 0 is unbuffered. |
86 | */ |
87 | public int getBufferSize() { return bufferSize; } |
88 | |
89 | /** |
90 | * This method indicates whether the JspWriter is autoFlushing. |
91 | * |
92 | * @return if this JspWriter is auto flushing or throwing IOExceptions on |
93 | * buffer overflow conditions |
94 | */ |
95 | public boolean isAutoFlush() { return autoFlush; } |
96 | |
97 | /** |
98 | * Create a new buffered character-output stream that uses an output |
99 | * buffer of the given size. |
100 | * |
101 | * @param response A Servlet Response |
102 | * @param sz Output-buffer size, a positive integer |
103 | * |
104 | * @exception IllegalArgumentException If sz is <= 0 |
105 | */ |
106 | public VelocityWriter(Writer writer, int sz, boolean autoFlush) |
107 | { |
108 | this(sz, autoFlush); |
109 | if (sz < 0) |
110 | throw new IllegalArgumentException("Buffer size <= 0"); |
111 | this.writer = writer; |
112 | cb = sz == 0 ? null : new char[sz]; |
113 | nextChar = 0; |
114 | } |
115 | |
116 | private final void init( Writer writer, int sz, boolean autoFlush ) |
117 | { |
118 | this.writer= writer; |
119 | if( sz > 0 && ( cb == null || sz > cb.length ) ) |
120 | cb=new char[sz]; |
121 | nextChar = 0; |
122 | this.autoFlush=autoFlush; |
123 | this.bufferSize=sz; |
124 | } |
125 | |
126 | /** |
127 | * Flush the output buffer to the underlying character stream, without |
128 | * flushing the stream itself. This method is non-private only so that it |
129 | * may be invoked by PrintStream. |
130 | */ |
131 | private final void flushBuffer() throws IOException |
132 | { |
133 | if (bufferSize == 0) |
134 | return; |
135 | flushed = true; |
136 | if (nextChar == 0) |
137 | return; |
138 | writer.write(cb, 0, nextChar); |
139 | nextChar = 0; |
140 | } |
141 | |
142 | /** |
143 | * Discard the output buffer. |
144 | */ |
145 | public final void clear() |
146 | { |
147 | nextChar = 0; |
148 | } |
149 | |
150 | private final void bufferOverflow() throws IOException |
151 | { |
152 | throw new IOException("overflow"); |
153 | } |
154 | |
155 | /** |
156 | * Flush the stream. |
157 | * |
158 | */ |
159 | public final void flush() throws IOException |
160 | { |
161 | flushBuffer(); |
162 | if (writer != null) |
163 | { |
164 | writer.flush(); |
165 | } |
166 | } |
167 | |
168 | /** |
169 | * Close the stream. |
170 | * |
171 | */ |
172 | public final void close() throws IOException { |
173 | if (writer == null) |
174 | return; |
175 | flush(); |
176 | } |
177 | |
178 | /** |
179 | * @return the number of bytes unused in the buffer |
180 | */ |
181 | public final int getRemaining() |
182 | { |
183 | return bufferSize - nextChar; |
184 | } |
185 | |
186 | /** |
187 | * Write a single character. |
188 | * |
189 | */ |
190 | public final void write(int c) throws IOException |
191 | { |
192 | if (bufferSize == 0) |
193 | { |
194 | writer.write(c); |
195 | } |
196 | else |
197 | { |
198 | if (nextChar >= bufferSize) |
199 | if (autoFlush) |
200 | flushBuffer(); |
201 | else |
202 | bufferOverflow(); |
203 | cb[nextChar++] = (char) c; |
204 | } |
205 | } |
206 | |
207 | /** |
208 | * Our own little min method, to avoid loading |
209 | * <code>java.lang.Math</code> if we've run out of file |
210 | * descriptors and we're trying to print a stack trace. |
211 | */ |
212 | private final int min(int a, int b) |
213 | { |
214 | return (a < b ? a : b); |
215 | } |
216 | |
217 | /** |
218 | * Write a portion of an array of characters. |
219 | * |
220 | * <p> Ordinarily this method stores characters from the given array into |
221 | * this stream's buffer, flushing the buffer to the underlying stream as |
222 | * needed. If the requested length is at least as large as the buffer, |
223 | * however, then this method will flush the buffer and write the characters |
224 | * directly to the underlying stream. Thus redundant |
225 | * <code>DiscardableBufferedWriter</code>s will not copy data unnecessarily. |
226 | * |
227 | * @param cbuf A character array |
228 | * @param off Offset from which to start reading characters |
229 | * @param len Number of characters to write |
230 | * |
231 | */ |
232 | public final void write(char cbuf[], int off, int len) |
233 | throws IOException |
234 | { |
235 | if (bufferSize == 0) |
236 | { |
237 | writer.write(cbuf, off, len); |
238 | return; |
239 | } |
240 | |
241 | if (len == 0) |
242 | { |
243 | return; |
244 | } |
245 | |
246 | if (len >= bufferSize) |
247 | { |
248 | /* If the request length exceeds the size of the output buffer, |
249 | flush the buffer and then write the data directly. In this |
250 | way buffered streams will cascade harmlessly. */ |
251 | if (autoFlush) |
252 | flushBuffer(); |
253 | else |
254 | bufferOverflow(); |
255 | writer.write(cbuf, off, len); |
256 | return; |
257 | } |
258 | |
259 | int b = off, t = off + len; |
260 | while (b < t) |
261 | { |
262 | int d = min(bufferSize - nextChar, t - b); |
263 | System.arraycopy(cbuf, b, cb, nextChar, d); |
264 | b += d; |
265 | nextChar += d; |
266 | if (nextChar >= bufferSize) |
267 | if (autoFlush) |
268 | flushBuffer(); |
269 | else |
270 | bufferOverflow(); |
271 | } |
272 | } |
273 | |
274 | /** |
275 | * Write an array of characters. This method cannot be inherited from the |
276 | * Writer class because it must suppress I/O exceptions. |
277 | */ |
278 | public final void write(char buf[]) throws IOException |
279 | { |
280 | write(buf, 0, buf.length); |
281 | } |
282 | |
283 | /** |
284 | * Write a portion of a String. |
285 | * |
286 | * @param s String to be written |
287 | * @param off Offset from which to start reading characters |
288 | * @param len Number of characters to be written |
289 | * |
290 | */ |
291 | public final void write(String s, int off, int len) throws IOException |
292 | { |
293 | if (bufferSize == 0) |
294 | { |
295 | writer.write(s, off, len); |
296 | return; |
297 | } |
298 | int b = off, t = off + len; |
299 | while (b < t) |
300 | { |
301 | int d = min(bufferSize - nextChar, t - b); |
302 | s.getChars(b, b + d, cb, nextChar); |
303 | b += d; |
304 | nextChar += d; |
305 | if (nextChar >= bufferSize) |
306 | if (autoFlush) |
307 | flushBuffer(); |
308 | else |
309 | bufferOverflow(); |
310 | } |
311 | } |
312 | |
313 | /** |
314 | * Write a string. This method cannot be inherited from the Writer class |
315 | * because it must suppress I/O exceptions. |
316 | */ |
317 | public final void write(String s) throws IOException |
318 | { |
319 | write(s, 0, s.length()); |
320 | } |
321 | |
322 | /** |
323 | * resets this class so that it can be reused |
324 | * |
325 | */ |
326 | public final void recycle( Writer writer) |
327 | { |
328 | this.writer = writer; |
329 | flushed = false; |
330 | clear(); |
331 | } |
332 | } |