1   //========================================================================
2   //$Id: HttpGenerator.java,v 1.7 2005/11/25 21:17:12 gregwilkins Exp $
3   //Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
4   //------------------------------------------------------------------------
5   //Licensed under the Apache License, Version 2.0 (the "License");
6   //you may not use this file except in compliance with the License.
7   //You may obtain a copy of the License at 
8   //http://www.apache.org/licenses/LICENSE-2.0
9   //Unless required by applicable law or agreed to in writing, software
10  //distributed under the License is distributed on an "AS IS" BASIS,
11  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  //See the License for the specific language governing permissions and
13  //limitations under the License.
14  //========================================================================
15  
16  package org.mortbay.jetty;
17  
18  import java.io.IOException;
19  import java.io.OutputStreamWriter;
20  import java.io.Writer;
21  import java.lang.reflect.Field;
22  import java.lang.reflect.Modifier;
23  
24  import javax.servlet.ServletOutputStream;
25  import javax.servlet.http.HttpServletResponse;
26  
27  import org.mortbay.io.Buffer;
28  import org.mortbay.io.Buffers;
29  import org.mortbay.io.ByteArrayBuffer;
30  import org.mortbay.io.EndPoint;
31  import org.mortbay.io.View;
32  import org.mortbay.log.Log;
33  import org.mortbay.util.ByteArrayOutputStream2;
34  import org.mortbay.util.StringUtil;
35  import org.mortbay.util.TypeUtil;
36  
37  /* ------------------------------------------------------------ */
38  /**
39   * Abstract Generator. Builds HTTP Messages.
40   * 
41   * Currently this class uses a system parameter "jetty.direct.writers" to control
42   * two optional writer to byte conversions. buffer.writers=true will probably be 
43   * faster, but will consume more memory.   This option is just for testing and tuning.
44   * 
45   * @author gregw
46   * 
47   */
48  public abstract class AbstractGenerator implements Generator
49  {
50      // states
51      public final static int STATE_HEADER = 0;
52      public final static int STATE_CONTENT = 2;
53      public final static int STATE_FLUSHING = 3;
54      public final static int STATE_END = 4;
55      
56      private static byte[] NO_BYTES = {};
57      private static int MAX_OUTPUT_CHARS = 512; 
58  
59      private static Buffer[] __reasons = new Buffer[505];
60      static
61      {
62          Field[] fields = HttpServletResponse.class.getDeclaredFields();
63          for (int i=0;i<fields.length;i++)
64          {
65              if ((fields[i].getModifiers()&Modifier.STATIC)!=0 &&
66                   fields[i].getName().startsWith("SC_"))
67              {
68                  try
69                  {
70                      int code = fields[i].getInt(null);
71                      if (code<__reasons.length)
72                          __reasons[code]=new ByteArrayBuffer(fields[i].getName().substring(3));
73                  }
74                  catch(IllegalAccessException e)
75                  {}
76              }    
77          }
78      }
79      
80      protected static Buffer getReasonBuffer(int code)
81      {
82          Buffer reason=(code<__reasons.length)?__reasons[code]:null;
83          return reason==null?null:reason;
84      }
85      
86      public static String getReason(int code)
87      {
88          Buffer reason=(code<__reasons.length)?__reasons[code]:null;
89          return reason==null?TypeUtil.toString(code):reason.toString();
90      }
91  
92      // data
93      protected int _state = STATE_HEADER;
94      
95      protected int _status = 0;
96      protected int _version = HttpVersions.HTTP_1_1_ORDINAL;
97      protected  Buffer _reason;
98      protected  Buffer _method;
99      protected  String _uri;
100 
101     protected long _contentWritten = 0;
102     protected long _contentLength = HttpTokens.UNKNOWN_CONTENT;
103     protected boolean _last = false;
104     protected boolean _head = false;
105     protected boolean _noContent = false;
106     protected boolean _close = false;
107 
108     protected Buffers _buffers; // source of buffers
109     protected EndPoint _endp;
110 
111     protected int _headerBufferSize;
112     protected int _contentBufferSize;
113     
114     protected Buffer _header; // Buffer for HTTP header (and maybe small _content)
115     protected Buffer _buffer; // Buffer for copy of passed _content
116     protected Buffer _content; // Buffer passed to addContent
117     
118     private boolean _sendServerVersion;
119 
120     
121     /* ------------------------------------------------------------------------------- */
122     /**
123      * Constructor.
124      * 
125      * @param buffers buffer pool
126      * @param headerBufferSize Size of the buffer to allocate for HTTP header
127      * @param contentBufferSize Size of the buffer to allocate for HTTP content
128      */
129     public AbstractGenerator(Buffers buffers, EndPoint io, int headerBufferSize, int contentBufferSize)
130     {
131         this._buffers = buffers;
132         this._endp = io;
133         _headerBufferSize=headerBufferSize;
134         _contentBufferSize=contentBufferSize;
135     }
136 
137     /* ------------------------------------------------------------------------------- */
138     public void reset(boolean returnBuffers)
139     {
140         _state = STATE_HEADER;
141         _status = 0;
142         _version = HttpVersions.HTTP_1_1_ORDINAL;
143         _reason = null;
144         _last = false;
145         _head = false;
146         _noContent=false;
147         _close = false;
148         _contentWritten = 0;
149         _contentLength = HttpTokens.UNKNOWN_CONTENT;
150 
151         synchronized(this)
152         {
153             if (returnBuffers)
154             {
155                 if (_header != null) 
156                     _buffers.returnBuffer(_header);
157                 _header = null;
158                 if (_buffer != null) 
159                     _buffers.returnBuffer(_buffer);
160                 _buffer = null;
161             }
162             else
163             {
164                 if (_header != null) 
165                     _header.clear();
166 
167                 if (_buffer != null)
168                 {
169                     _buffers.returnBuffer(_buffer);
170                     _buffer = null;
171                 }
172             }
173         }
174         _content = null;
175         _method=null;
176     }
177 
178     /* ------------------------------------------------------------------------------- */
179     public void resetBuffer()
180     {                   
181         if(_state>=STATE_FLUSHING)
182             throw new IllegalStateException("Flushed");
183         
184         _last = false;
185         _close = false;
186         _contentWritten = 0;
187         _contentLength = HttpTokens.UNKNOWN_CONTENT;
188         _content=null;
189         if (_buffer!=null)
190             _buffer.clear();  
191     }
192 
193     /* ------------------------------------------------------------ */
194     /**
195      * @return Returns the contentBufferSize.
196      */
197     public int getContentBufferSize()
198     {
199         return _contentBufferSize;
200     }
201 
202     /* ------------------------------------------------------------ */
203     /**
204      * @param contentBufferSize The contentBufferSize to set.
205      */
206     public void increaseContentBufferSize(int contentBufferSize)
207     {
208         if (contentBufferSize > _contentBufferSize)
209         {
210             _contentBufferSize = contentBufferSize;
211             if (_buffer != null)
212             {
213                 Buffer nb = _buffers.getBuffer(_contentBufferSize);
214                 nb.put(_buffer);
215                 _buffers.returnBuffer(_buffer);
216                 _buffer = nb;
217             }
218         }
219     }
220     
221     /* ------------------------------------------------------------ */    
222     public Buffer getUncheckedBuffer()
223     {
224         return _buffer;
225     }
226     
227     /* ------------------------------------------------------------ */    
228     public boolean getSendServerVersion ()
229     {
230         return _sendServerVersion;
231     }
232     
233     /* ------------------------------------------------------------ */    
234     public void setSendServerVersion (boolean sendServerVersion)
235     {
236         _sendServerVersion = sendServerVersion;
237     }
238     
239     /* ------------------------------------------------------------ */
240     public int getState()
241     {
242         return _state;
243     }
244 
245     /* ------------------------------------------------------------ */
246     public boolean isState(int state)
247     {
248         return _state == state;
249     }
250 
251     /* ------------------------------------------------------------ */
252     public boolean isComplete()
253     {
254         return _state == STATE_END;
255     }
256 
257     /* ------------------------------------------------------------ */
258     public boolean isIdle()
259     {
260         return _state == STATE_HEADER && _method==null && _status==0;
261     }
262 
263     /* ------------------------------------------------------------ */
264     public boolean isCommitted()
265     {
266         return _state != STATE_HEADER;
267     }
268 
269     /* ------------------------------------------------------------ */
270     /**
271      * @return Returns the head.
272      */
273     public boolean isHead()
274     {
275         return _head;
276     }
277 
278     /* ------------------------------------------------------------ */
279     public void setContentLength(long value)
280     {
281         if (value<0)
282             _contentLength=HttpTokens.UNKNOWN_CONTENT;
283         else
284             _contentLength=value;
285     }
286     
287     /* ------------------------------------------------------------ */
288     /**
289      * @param head The head to set.
290      */
291     public void setHead(boolean head)
292     {
293         _head = head;
294     }
295 
296     /* ------------------------------------------------------------ */
297     /**
298      * @return <code>false</code> if the connection should be closed after a request has been read,
299      * <code>true</code> if it should be used for additional requests.
300      */
301     public boolean isPersistent()
302     {
303         return !_close;
304     }
305 
306     /* ------------------------------------------------------------ */
307     public void setPersistent(boolean persistent)
308     {
309         _close=!persistent;
310     }
311 
312     /* ------------------------------------------------------------ */
313     /**
314      * @param version The version of the client the response is being sent to (NB. Not the version
315      *            in the response, which is the version of the server).
316      */
317     public void setVersion(int version)
318     {
319         if (_state != STATE_HEADER) 
320             throw new IllegalStateException("STATE!=START "+_state);
321         _version = version;
322         if (_version==HttpVersions.HTTP_0_9_ORDINAL && _method!=null)
323             _noContent=true;
324     }
325 
326     /* ------------------------------------------------------------ */
327     public int getVersion()
328     {
329         return _version;
330     }
331     
332     /* ------------------------------------------------------------ */
333     /**
334      */
335     public void setRequest(String method, String uri)
336     {
337         if (method==null || HttpMethods.GET.equals(method) )
338             _method=HttpMethods.GET_BUFFER;
339         else
340             _method=HttpMethods.CACHE.lookup(method);
341         _uri=uri;
342         if (_version==HttpVersions.HTTP_0_9_ORDINAL)
343             _noContent=true;
344     }
345 
346     /* ------------------------------------------------------------ */
347     /**
348      * @param status The status code to send.
349      * @param reason the status message to send.
350      */
351     public void setResponse(int status, String reason)
352     {
353         if (_state != STATE_HEADER) throw new IllegalStateException("STATE!=START");
354         _method=null;
355         _status = status;
356         if (reason!=null)
357         {
358             int len=reason.length();
359             if (len>_headerBufferSize/2)
360                 len=_headerBufferSize/2;
361             _reason=new ByteArrayBuffer(len);
362             for (int i=0;i<len;i++)
363             {
364                 char ch = reason.charAt(i);
365                 if (ch!='\r'&&ch!='\n')
366                     _reason.put((byte)ch);
367                 else
368                     _reason.put((byte)' ');
369             }
370         }
371     }
372 
373     /* ------------------------------------------------------------ */
374     /** Prepare buffer for unchecked writes.
375      * Prepare the generator buffer to receive unchecked writes
376      * @return the available space in the buffer.
377      * @throws IOException
378      */
379     protected abstract int prepareUncheckedAddContent() throws IOException;
380 
381     /* ------------------------------------------------------------ */
382     void uncheckedAddContent(int b)
383     {
384         _buffer.put((byte)b);
385     }
386 
387     /* ------------------------------------------------------------ */
388     void completeUncheckedAddContent()
389     {
390         if (_noContent)
391         {
392             if(_buffer!=null)
393                 _buffer.clear();
394             return;
395         }
396         else 
397         {
398             _contentWritten+=_buffer.length();
399             if (_head)
400                 _buffer.clear();
401         }
402     }
403     
404     /* ------------------------------------------------------------ */
405     public boolean isBufferFull()
406     {
407         // Should we flush the buffers?
408         boolean full =  
409             (_buffer != null && _buffer.space() == 0) ||
410             (_content!=null && _content.length()>0);
411              
412         return full;
413     }
414     
415     /* ------------------------------------------------------------ */
416     public boolean isContentWritten()
417     {
418         return _contentLength>=0 && _contentWritten>=_contentLength;
419     }
420     
421     /* ------------------------------------------------------------ */
422     public abstract void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException;
423     
424     /* ------------------------------------------------------------ */
425     /**
426      * Complete the message.
427      * 
428      * @throws IOException
429      */
430     public void complete() throws IOException
431     {
432         if (_state == STATE_HEADER)
433         {
434             throw new IllegalStateException("State==HEADER");
435         }
436 
437         if (_contentLength >= 0 && _contentLength != _contentWritten && !_head)
438         {
439             if (Log.isDebugEnabled())
440                 Log.debug("ContentLength written=="+_contentWritten+" != contentLength=="+_contentLength);
441             _close = true;
442         }
443     }
444 
445     /* ------------------------------------------------------------ */
446     public abstract long flush() throws IOException;
447     
448 
449     /* ------------------------------------------------------------ */
450     /**
451      * Utility method to send an error response. If the builder is not committed, this call is
452      * equivalent to a setResponse, addcontent and complete call.
453      * 
454      * @param code
455      * @param reason
456      * @param content
457      * @param close
458      * @throws IOException
459      */
460     public void sendError(int code, String reason, String content, boolean close) throws IOException
461     {
462         if (!isCommitted())
463         {
464             setResponse(code, reason);
465             _close = close;
466             completeHeader(null, false);
467             if (content != null) 
468                 addContent(new View(new ByteArrayBuffer(content)), Generator.LAST);
469             complete();
470         }
471     }
472 
473     /* ------------------------------------------------------------ */
474     /**
475      * @return Returns the contentWritten.
476      */
477     public long getContentWritten()
478     {
479         return _contentWritten;
480     }
481 
482 
483     /* ------------------------------------------------------------ */
484     /* ------------------------------------------------------------ */
485     /* ------------------------------------------------------------ */
486     /* ------------------------------------------------------------ */
487     /** Output.
488      * 
489      * <p>
490      * Implements  {@link javax.servlet.ServletOutputStream} from the {@link javax.servlet} package.   
491      * </p>
492      * A {@link ServletOutputStream} implementation that writes content
493      * to a {@link AbstractGenerator}.   The class is designed to be reused
494      * and can be reopened after a close.
495      */
496     public static class Output extends ServletOutputStream 
497     {
498         protected AbstractGenerator _generator;
499         protected long _maxIdleTime;
500         protected ByteArrayBuffer _buf = new ByteArrayBuffer(NO_BYTES);
501         protected boolean _closed;
502         
503         // These are held here for reuse by Writer
504         String _characterEncoding;
505         Writer _converter;
506         char[] _chars;
507         ByteArrayOutputStream2 _bytes;
508         
509 
510         /* ------------------------------------------------------------ */
511         public Output(AbstractGenerator generator, long maxIdleTime)
512         {
513             _generator=generator;
514             _maxIdleTime=maxIdleTime;
515         }
516         
517         /* ------------------------------------------------------------ */
518         /*
519          * @see java.io.OutputStream#close()
520          */
521         public void close() throws IOException
522         {
523             _closed=true;
524         }
525 
526         /* ------------------------------------------------------------ */
527         void  blockForOutput() throws IOException
528         {
529             if (_generator._endp.isBlocking())
530             {
531                 try
532                 {
533                     flush();
534                 }
535                 catch(IOException e)
536                 {
537                     _generator._endp.close();
538                     throw e;
539                 }
540             }
541             else
542             {
543                 if (!_generator._endp.blockWritable(_maxIdleTime))
544                 {
545                     _generator._endp.close();
546                     throw new EofException("timeout");
547                 }
548                 
549                 _generator.flush();
550             }
551         }
552         
553         /* ------------------------------------------------------------ */
554         void reopen()
555         {
556             _closed=false;
557         }
558         
559         /* ------------------------------------------------------------ */
560         public void flush() throws IOException
561         {
562             // block until everything is flushed
563             Buffer content = _generator._content;
564             Buffer buffer = _generator._buffer;
565             if (content!=null && content.length()>0 ||buffer!=null && buffer.length()>0)
566             {
567                 _generator.flush();
568                 
569                 while ((content!=null && content.length()>0 ||buffer!=null && buffer.length()>0) && _generator._endp.isOpen())
570                     blockForOutput();
571             }
572         }
573 
574         /* ------------------------------------------------------------ */
575         public void write(byte[] b, int off, int len) throws IOException
576         {
577             _buf.wrap(b, off, len);
578             write(_buf);
579         }
580 
581         /* ------------------------------------------------------------ */
582         /*
583          * @see java.io.OutputStream#write(byte[])
584          */
585         public void write(byte[] b) throws IOException
586         {
587             _buf.wrap(b);
588             write(_buf);
589         }
590 
591         /* ------------------------------------------------------------ */
592         /*
593          * @see java.io.OutputStream#write(int)
594          */
595         public void write(int b) throws IOException
596         {
597             if (_closed)
598                 throw new IOException("Closed");
599             if (!_generator._endp.isOpen())
600                 throw new EofException();
601             
602             // Block until we can add _content.
603             while (_generator.isBufferFull())
604             {
605                 blockForOutput();
606                 if (_closed)
607                     throw new IOException("Closed");
608                 if (!_generator._endp.isOpen())
609                     throw new EofException();
610             }
611 
612             // Add the _content
613             if (_generator.addContent((byte)b))
614                 // Buffers are full so flush.
615                 flush();
616            
617             if (_generator.isContentWritten())
618             {
619                 flush();
620                 close();
621             }
622         }
623 
624         /* ------------------------------------------------------------ */
625         private void write(Buffer buffer) throws IOException
626         {
627             if (_closed)
628                 throw new IOException("Closed");
629             if (!_generator._endp.isOpen())
630                 throw new EofException();
631             
632             // Block until we can add _content.
633             while (_generator.isBufferFull())
634             {
635                 blockForOutput();
636                 if (_closed)
637                     throw new IOException("Closed");
638                 if (!_generator._endp.isOpen())
639                     throw new EofException();
640             }
641 
642             // Add the _content
643             _generator.addContent(buffer, Generator.MORE);
644 
645             // Have to flush and complete headers?
646             if (_generator.isBufferFull())
647                 flush();
648             
649             if (_generator.isContentWritten())
650             {
651                 flush();
652                 close();
653             }
654 
655             // Block until our buffer is free
656             while (buffer.length() > 0 && _generator._endp.isOpen())
657                 blockForOutput();
658         }
659 
660         /* ------------------------------------------------------------ */
661         /* 
662          * @see javax.servlet.ServletOutputStream#print(java.lang.String)
663          */
664         public void print(String s) throws IOException
665         {
666             write(s.getBytes());
667         }
668     }
669     
670     /* ------------------------------------------------------------ */
671     /* ------------------------------------------------------------ */
672     /* ------------------------------------------------------------ */
673     /** OutputWriter.
674      * A writer that can wrap a {@link Output} stream and provide
675      * character encodings.
676      *
677      * The UTF-8 encoding is done by this class and no additional 
678      * buffers or Writers are used.
679      * The UTF-8 code was inspired by http://javolution.org
680      */
681     public static class OutputWriter extends Writer
682     {
683         private static final int WRITE_CONV = 0;
684         private static final int WRITE_ISO1 = 1;
685         private static final int WRITE_UTF8 = 2;
686         
687         Output _out;
688         AbstractGenerator _generator;
689         int _writeMode;
690         int _surrogate;
691 
692         /* ------------------------------------------------------------ */
693         public OutputWriter(Output out)
694         {
695             _out=out;
696             _generator=_out._generator;
697              
698         }
699 
700         /* ------------------------------------------------------------ */
701         public void setCharacterEncoding(String encoding)
702         {
703             if (encoding == null || StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding))
704             {
705                 _writeMode = WRITE_ISO1;
706             }
707             else if (StringUtil.__UTF8.equalsIgnoreCase(encoding))
708             {
709                 _writeMode = WRITE_UTF8;
710             }
711             else
712             {
713                 _writeMode = WRITE_CONV;
714                 if (_out._characterEncoding == null || !_out._characterEncoding.equalsIgnoreCase(encoding))
715                     _out._converter = null; // Set lazily in getConverter()
716             }
717             
718             _out._characterEncoding = encoding;
719             if (_out._bytes==null)
720                 _out._bytes = new ByteArrayOutputStream2(MAX_OUTPUT_CHARS);
721         }
722 
723         /* ------------------------------------------------------------ */
724         public void close() throws IOException
725         {
726             _out.close();
727         }
728 
729         /* ------------------------------------------------------------ */
730         public void flush() throws IOException
731         {
732             _out.flush();
733         }
734 
735         /* ------------------------------------------------------------ */
736         public void write (String s,int offset, int length) throws IOException
737         {   
738             while (length > MAX_OUTPUT_CHARS)
739             {
740                 write(s, offset, MAX_OUTPUT_CHARS);
741                 offset += MAX_OUTPUT_CHARS;
742                 length -= MAX_OUTPUT_CHARS;
743             }
744 
745             if (_out._chars==null)
746             {
747                 _out._chars = new char[MAX_OUTPUT_CHARS]; 
748             }
749             char[] chars = _out._chars;
750             s.getChars(offset, offset + length, chars, 0);
751             write(chars, 0, length);
752         }
753 
754         /* ------------------------------------------------------------ */
755         public void write (char[] s,int offset, int length) throws IOException
756         {              
757             Output out = _out; 
758             
759             while (length > 0)
760             {  
761                 out._bytes.reset();
762                 int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
763 
764                 switch (_writeMode)
765                 {
766                     case WRITE_CONV:
767                     {
768                         Writer converter=getConverter();
769                         converter.write(s, offset, chars);
770                         converter.flush();
771                     }
772                     break;
773 
774                     case WRITE_ISO1:
775                     {
776                         byte[] buffer=out._bytes.getBuf();
777                         int bytes=out._bytes.getCount();
778                         
779                         if (chars>buffer.length-bytes)
780                             chars=buffer.length-bytes;
781 
782                         for (int i = 0; i < chars; i++)
783                         {
784                             int c = s[offset+i];
785                             buffer[bytes++]=(byte)(c<256?c:'?'); // ISO-1 and UTF-8 match for 0 - 255
786                         }
787                         if (bytes>=0)
788                             out._bytes.setCount(bytes);
789 
790                         break;
791                     }
792 
793                     case WRITE_UTF8:
794                     {
795                         byte[] buffer=out._bytes.getBuf();
796                         int bytes=out._bytes.getCount();
797          
798                         if (bytes+chars>buffer.length)
799                             chars=buffer.length-bytes;
800                         
801                         for (int i = 0; i < chars; i++)
802                         {
803                             int code = s[offset+i];
804 
805                             if ((code & 0xffffff80) == 0) 
806                             {
807                                 // 1b
808                                 buffer[bytes++]=(byte)(code);
809                             }
810                             else if((code&0xfffff800)==0)
811                             {
812                                 // 2b
813                                 if (bytes+2>buffer.length)
814                                 {
815                                     chars=i;
816                                     break;
817                                 }
818                                 buffer[bytes++]=(byte)(0xc0|(code>>6));
819                                 buffer[bytes++]=(byte)(0x80|(code&0x3f));
820 
821                                 if (bytes+chars-i-1>buffer.length)
822                                     chars-=1;
823                             }
824                             else if((code&0xffff0000)==0)
825                             {
826                                 // 3b
827                                 if (bytes+3>buffer.length)
828                                 {
829                                     chars=i;
830                                     break;
831                                 }
832                                 buffer[bytes++]=(byte)(0xe0|(code>>12));
833                                 buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
834                                 buffer[bytes++]=(byte)(0x80|(code&0x3f));
835 
836                                 if (bytes+chars-i-1>buffer.length)
837                                     chars-=2;
838                             }
839                             else if((code&0xff200000)==0)
840                             {
841                                 // 4b
842                                 if (bytes+4>buffer.length)
843                                 {
844                                     chars=i;
845                                     break;
846                                 }
847                                 buffer[bytes++]=(byte)(0xf0|(code>>18));
848                                 buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
849                                 buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
850                                 buffer[bytes++]=(byte)(0x80|(code&0x3f));
851 
852                                 if (bytes+chars-i-1>buffer.length)
853                                     chars-=3;
854                             }
855                             else if((code&0xf4000000)==0)
856                             {
857                                 // 5b
858                                 if (bytes+5>buffer.length)
859                                 {
860                                     chars=i;
861                                     break;
862                                 }
863                                 buffer[bytes++]=(byte)(0xf8|(code>>24));
864                                 buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
865                                 buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
866                                 buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
867                                 buffer[bytes++]=(byte)(0x80|(code&0x3f));
868 
869                                 if (bytes+chars-i-1>buffer.length)
870                                     chars-=4;
871                             }
872                             else if((code&0x80000000)==0)
873                             {
874                                 // 6b
875                                 if (bytes+6>buffer.length)
876                                 {
877                                     chars=i;
878                                     break;
879                                 }
880                                 buffer[bytes++]=(byte)(0xfc|(code>>30));
881                                 buffer[bytes++]=(byte)(0x80|((code>>24)&0x3f));
882                                 buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
883                                 buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
884                                 buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
885                                 buffer[bytes++]=(byte)(0x80|(code&0x3f));
886 
887                                 if (bytes+chars-i-1>buffer.length)
888                                     chars-=5;
889                             }
890                             else
891                             {
892                                 buffer[bytes++]=(byte)('?');
893                             }
894                         }
895                         out._bytes.setCount(bytes);
896                         break;
897                     }
898                     default:
899                         throw new IllegalStateException();
900                 }
901                 
902                 out._bytes.writeTo(out);
903                 length-=chars;
904                 offset+=chars;
905             }
906         }
907 
908         /* ------------------------------------------------------------ */
909         private Writer getConverter() throws IOException
910         {
911             if (_out._converter == null)
912                 _out._converter = new OutputStreamWriter(_out._bytes, _out._characterEncoding);
913             return _out._converter;
914         }   
915     }
916 }