View Javadoc

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 }