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