1 /*
2 * Copyright (C) 1998-2000 Semiotek Inc. All Rights Reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted under the terms of either of the following
6 * Open Source licenses:
7 *
8 * The GNU General Public License, version 2, or any later version, as
9 * published by the Free Software Foundation
10 * (http://www.fsf.org/copyleft/gpl.html);
11 *
12 * or
13 *
14 * The Semiotek Public License (http://webmacro.org/LICENSE.)
15 *
16 * This software is provided "as is", with NO WARRANTY, not even the
17 * implied warranties of fitness to purpose, or merchantability. You
18 * assume all risks and liabilities associated with its use.
19 *
20 * See www.webmacro.org for more information on the WebMacro project.
21 */
22
23
24 package org.melati.template.webmacro;
25
26 import java.io.BufferedOutputStream;
27 import java.io.IOException;
28 import java.io.OutputStream;
29 import java.io.OutputStreamWriter;
30 import java.io.UnsupportedEncodingException;
31 import java.io.Writer;
32
33 import org.webmacro.Broker;
34 import org.webmacro.ResourceException;
35 import org.webmacro.util.Encoder;
36 import org.webmacro.util.EncoderProvider;
37
38
39 /**
40 * FastWriter attempts to optimize output speed in a WebMacro template
41 * through several specific optimizations:
42 * <ul>
43 * <li> FastWriter caches the output in a byte array until you
44 * call reset(). You can access the output by one of several
45 * methods: toString(), toByteArray(), or writeTo(OutputStream)
46 * <li> you can use a unicode conversion cache by calling writeStatic()
47 * <li> you can get the contents written to the FastWriter back
48 * as an array of bytes INSTEAD of writing to the output stream
49 * </ul>
50 * <p>
51 * <b>Note that the FastWriter requires an explicit flush</b>
52 * <p>
53 *
54 * @author Marcel Huijkman
55 *
56 * @version 27-07-2002
57 */
58
59 public class FastWriter extends Writer
60 {
61
62 /**
63 * This encoding is either UTF16-BE or, if the platform does not
64 * support it, UTF8. It is a Unicode encoding which can have
65 * encoded strings concatenated together.
66 */
67 public static final String SAFE_UNICODE_ENCODING;
68
69 // find the safe encoding
70 static
71 {
72 String encoding = "UTF16-BE";
73 try
74 {
75 encoding.getBytes(encoding);
76 }
77 catch (Exception e)
78 {
79 encoding = "UTF8";
80 }
81 SAFE_UNICODE_ENCODING = encoding;
82 }
83
84
85 private final int DEFAULT_BUFFER_SIZE;
86 private final String _encoding; // what encoding we use
87 private final Writer _bwriter;
88 //private final ByteBufferOutputStream _bstream;
89 private final BufferedOutputStream _bstream;
90 private final Encoder _encoder;
91
92 private OutputStream _out;
93
94 private char[] _cbuf = new char[512];
95 private boolean _buffered;
96
97 /**
98 * Create a FastWriter to the target outputstream. You must specify
99 * a character encoding. You can also call writeTo(), toString(),
100 * and toByteArray() to access any un-flush()ed contents.
101 */
102 public FastWriter (Broker broker, OutputStream out, String encoding)
103 throws UnsupportedEncodingException
104 {
105 DEFAULT_BUFFER_SIZE = broker.getSettings().getIntegerSetting("FastWriter.DefaultBufferSize", 4096);
106 _encoding = hackEncoding(encoding);
107 //_bstream = new ByteBufferOutputStream(DEFAULT_BUFFER_SIZE);
108 _bstream = new BufferedOutputStream(out, DEFAULT_BUFFER_SIZE);
109 _bwriter = new OutputStreamWriter(_bstream, _encoding);
110
111
112 // fetch our encoder from the broker
113 try
114 {
115 _encoder = (Encoder) broker.get(EncoderProvider.TYPE, _encoding);
116 }
117 catch (ResourceException re)
118 {
119 throw new UnsupportedEncodingException(re.getMessage());
120 }
121
122 _buffered = false;
123
124 _out = out;
125 }
126
127 /**
128 * Workaround for problems with resin-2.0.3, which
129 * gives CPxxxx as a character encoding, but java
130 * knows only Cpxxxx. This method converts encoding
131 * to a form understood by java.
132 * <br>
133 * We should remove it some time after resin
134 * has been fixed
135 */
136 private static String hackEncoding (String encoding)
137 {
138 if (encoding.toLowerCase().startsWith("cp") &&
139 !encoding.startsWith("Cp"))
140 {
141 encoding = "Cp".concat(encoding.substring(2));
142 }
143 return encoding;
144 }
145
146 /**
147 * Create a new FastWriter with no output stream target. You can
148 * still call writeTo(), toString(), and toByteArray().
149 */
150 public FastWriter (Broker broker, String encoding)
151 throws java.io.UnsupportedEncodingException
152 {
153 this(broker, null, encoding);
154 }
155
156
157 /**
158 * Get the character encoding this FastWriter uses to convert
159 * characters to byte[]
160 */
161 public String getEncoding ()
162 {
163 return _encoding;
164 }
165
166 /**
167 * Get the encoder used by this FastWriter to transform
168 * char[] data into byte[] data.
169 */
170 public Encoder getEncoder ()
171 {
172 return _encoder;
173 }
174
175 /**
176 * Get the output stream this FastWriter sends output to. It
177 * may be null, in which case output is not sent anywhere.
178 */
179 public OutputStream getOutputStream ()
180 {
181 //return _out;
182 return _bstream;
183 }
184
185 /**
186 * Write characters to the output stream performing slow unicode
187 * conversion unless AsciiHack is on.
188 */
189 public void write (char[] cbuf) throws java.io.IOException
190 {
191 _bwriter.write(cbuf, 0, cbuf.length);
192 _buffered = true;
193 }
194
195 /**
196 * Write characters to to the output stream performing slow unicode
197 * conversion.
198 */
199 public void write (char[] cbuf, int offset, int len) throws java.io.IOException
200 {
201 _bwriter.write(cbuf, offset, len);
202 _buffered = true;
203 }
204
205 /**
206 * Write a single character, performing slow unicode conversion
207 */
208 public void write (int c) throws java.io.IOException
209 {
210 _bwriter.write(c);
211 _buffered = true;
212 }
213
214 /**
215 * Write a string to the underlying output stream, performing
216 * unicode conversion.
217 */
218 public void write (final String s) throws java.io.IOException
219 {
220 final int len = s.length();
221 try
222 {
223 s.getChars(0, len, _cbuf, 0);
224 }
225 catch (IndexOutOfBoundsException e)
226 {
227 _cbuf = new char[len + (len - _cbuf.length)];
228 s.getChars(0, len, _cbuf, 0);
229 }
230
231 _bwriter.write(_cbuf, 0, len);
232 _buffered = true;
233 }
234
235 /**
236 * Write a string to the underlying output stream, performing
237 * unicode conversion.
238 */
239 public void write (final String s, final int off, final int len) throws java.io.IOException
240 {
241 try
242 {
243 s.getChars(off, off + len, _cbuf, 0);
244 }
245 catch (IndexOutOfBoundsException e)
246 {
247 _cbuf = new char[len + (len - _cbuf.length)];
248 s.getChars(off, off + len, _cbuf, 0);
249 }
250
251 _bwriter.write(_cbuf, 0, len);
252 _buffered = true;
253 }
254
255 /**
256 * Write a string to the underlying output stream, performing
257 * unicode conversion if necessary--try and read the encoding
258 * from an encoding cache if possible.
259 */
260 public void writeStatic (final String s)
261 {
262 if (_buffered)
263 {
264 bflush();
265 }
266 try
267 {
268 byte[] b = _encoder.encode(s);
269 _bstream.write(b, 0, b.length);
270 }
271 catch (UnsupportedEncodingException uee)
272 {
273 // this should never happen
274 uee.printStackTrace();
275 }
276 catch (java.io.IOException ioe)
277 {
278 // this should never happen
279 ioe.printStackTrace();
280 }
281 }
282
283 /**
284 * Write raw bytes to the underlying stream. These bytes must be
285 * properly encoded with the encoding returned by getEncoding().
286 */
287 public void write (byte[] rawBytes)
288 {
289 if (_buffered)
290 {
291 bflush();
292 }
293 try {
294 _bstream.write(rawBytes);
295 }
296 catch (java.io.IOException ioe)
297 {
298 // this should never happen
299 ioe.printStackTrace();
300 }
301 }
302
303 /**
304 * Write raw bytes to the underlying stream. Tehse bytes must be
305 * properly encoded witht he encoding returned by getEncoding()
306 */
307 public void write (byte[] rawBytes, int offset, int len)
308 {
309 if (_buffered)
310 {
311 bflush();
312 }
313 try {
314 _bstream.write(rawBytes, offset, len);
315 }
316 catch (java.io.IOException ioe)
317 {
318 // this should never happen
319 ioe.printStackTrace();
320 }
321
322 }
323
324 private void bflush ()
325 {
326 try
327 {
328 _bwriter.flush();
329 _buffered = false;
330 }
331 catch (IOException e)
332 {
333 e.printStackTrace();
334 }
335 }
336
337
338 /**
339 * Flush all data out to the OutputStream, if any, clearing
340 * the internal buffers. Note that data is ONLY written to
341 * the output stream on a flush() operation, and never at
342 * any other time. Consequently this is one of the few places
343 * that you may actually encounter an IOException when using
344 * the FastWriter class.
345 */
346 public void flush () throws IOException
347 {
348 if (_buffered)
349 {
350 bflush();
351 }
352
353 if (_out != null)
354 {
355 //writeTo(_out);
356 _out.flush();
357 }
358 //_bstream.reset();
359 }
360
361 /**
362 * Return the number of bytes that would be written out if flush()
363 * is called.
364 */
365 public int size () {
366 if (_buffered)
367 {
368 bflush();
369 }
370 return 0;
371 //return _bstream.size();
372 }
373
374 /**
375 * Copy the contents written so far into a byte array.
376 */
377 public byte[] toByteArray ()
378 {
379 if (_buffered)
380 {
381 bflush();
382 }
383 return null;
384 //return _bstream.getBytes();
385 }
386
387 /**
388 * Copy the contents written so far into a String.
389 */
390 public String toString ()
391 {
392 if (_buffered)
393 {
394 bflush();
395 }
396 return null;
397 }
398
399 // try
400 // {
401 //return _bstream.toString(_encoding);
402 // return null;
403 // }
404 //catch (UnsupportedEncodingException e)
405 //{
406 // e.printStackTrace(); // never happen: we already used it
407 // return null;
408 // }
409 //}
410
411 /**
412 * Copy the contents written so far to the suppiled output stream
413 */
414 public void writeTo (OutputStream out) {
415 if (_buffered)
416 {
417 bflush();
418 }
419 OutputStream foolEclipse = out;
420 out = foolEclipse;
421 //_bstream.writeTo(out);
422 }
423
424 /**
425 * Reset the fastwriter, clearing any contents that have
426 * been generated so far.
427 */
428 public void reset (OutputStream out)
429 {
430 if (_buffered)
431 {
432 bflush();
433 }
434 //_bstream.reset();
435 _out = out;
436 }
437
438 /**
439 * Get a new FastWriter. You must then call writeTo(..) before
440 * attempting to write to the FastWriter.
441 */
442 public static FastWriter getInstance (Broker broker, OutputStream out,
443 String encoding)
444 throws UnsupportedEncodingException
445 {
446 return new FastWriter(broker, out, encoding);
447 }
448
449 /**
450 * Get a new FastWriter. You must then call writeTo(..) before
451 * attempting to write to the FastWriter.
452 */
453 public static FastWriter getInstance (Broker broker, OutputStream out)
454 throws UnsupportedEncodingException
455 {
456 return getInstance(broker, out, SAFE_UNICODE_ENCODING);
457 }
458
459 /**
460 * Return a FastWriter with the specified encoding and no output stream.
461 */
462 public static FastWriter getInstance (Broker broker, String encoding)
463 throws UnsupportedEncodingException
464 {
465 return getInstance(broker, null, encoding);
466 }
467
468 /**
469 * Return a FastWriter with default encoding and no output stream.
470 */
471 public static FastWriter getInstance (Broker broker)
472 {
473 try
474 {
475 return getInstance(broker, null, SAFE_UNICODE_ENCODING);
476 }
477 catch (UnsupportedEncodingException e)
478 {
479 e.printStackTrace(); // never gonna happen
480 return null;
481 }
482 }
483
484 public void close () throws IOException
485 {
486 flush();
487 if (_out != null)
488 {
489 //_out.close();
490 _out = null;
491 }
492 }
493 }
494