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 }