View Javadoc

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