View Javadoc

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 final 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) throw new IllegalStateException("STATE!=START");
320         _version = version;
321         if (_version==HttpVersions.HTTP_0_9_ORDINAL && _method!=null)
322             _noContent=true;
323     }
324 
325     /* ------------------------------------------------------------ */
326     public int getVersion()
327     {
328         return _version;
329     }
330     
331     /* ------------------------------------------------------------ */
332     /**
333      */
334     public void setRequest(String method, String uri)
335     {
336         if (method==null || HttpMethods.GET.equals(method) )
337             _method=HttpMethods.GET_BUFFER;
338         else
339             _method=HttpMethods.CACHE.lookup(method);
340         _uri=uri;
341         if (_version==HttpVersions.HTTP_0_9_ORDINAL)
342             _noContent=true;
343     }
344 
345     /* ------------------------------------------------------------ */
346     /**
347      * @param status The status code to send.
348      * @param reason the status message to send.
349      */
350     public void setResponse(int status, String reason)
351     {
352         if (_state != STATE_HEADER) throw new IllegalStateException("STATE!=START");
353 
354         _status = status;
355         if (reason!=null)
356         {
357             int len=reason.length();
358             if (len>_headerBufferSize/2)
359                 len=_headerBufferSize/2;
360             _reason=new ByteArrayBuffer(len);
361             for (int i=0;i<len;i++)
362             {
363                 char ch = reason.charAt(i);
364                 if (ch!='\r'&&ch!='\n')
365                     _reason.put((byte)ch);
366                 else
367                     _reason.put((byte)' ');
368             }
369         }
370     }
371 
372     /* ------------------------------------------------------------ */
373     /** Prepare buffer for unchecked writes.
374      * Prepare the generator buffer to receive unchecked writes
375      * @return the available space in the buffer.
376      * @throws IOException
377      */
378     protected abstract int prepareUncheckedAddContent() throws IOException;
379 
380     /* ------------------------------------------------------------ */
381     void uncheckedAddContent(int b)
382     {
383         _buffer.put((byte)b);
384     }
385 
386     /* ------------------------------------------------------------ */
387     void completeUncheckedAddContent()
388     {
389         if (_noContent)
390         {
391             if(_buffer!=null)
392                 _buffer.clear();
393             return;
394         }
395         else 
396         {
397             _contentWritten+=_buffer.length();
398             if (_head)
399                 _buffer.clear();
400         }
401     }
402     
403     /* ------------------------------------------------------------ */
404     public boolean isBufferFull()
405     {
406         if (_buffer != null && _buffer.space()==0)
407         {
408             if (_buffer.length()==0 && !_buffer.isImmutable())
409                 _buffer.compact();
410             return _buffer.space()==0;
411         }
412 
413         return _content!=null && _content.length()>0;
414     }
415     
416     /* ------------------------------------------------------------ */
417     public boolean isContentWritten()
418     {
419         return _contentLength>=0 && _contentWritten>=_contentLength;
420     }
421     
422     /* ------------------------------------------------------------ */
423     public abstract void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException;
424     
425     /* ------------------------------------------------------------ */
426     /**
427      * Complete the message.
428      * 
429      * @throws IOException
430      */
431     public void complete() throws IOException
432     {
433         if (_state == STATE_HEADER)
434         {
435             throw new IllegalStateException("State==HEADER");
436         }
437 
438         if (_contentLength >= 0 && _contentLength != _contentWritten && !_head)
439         {
440             if (Log.isDebugEnabled())
441                 Log.debug("ContentLength written=="+_contentWritten+" != contentLength=="+_contentLength);
442             _close = true;
443         }
444     }
445 
446     /* ------------------------------------------------------------ */
447     public abstract long flush() throws IOException;
448     
449 
450     /* ------------------------------------------------------------ */
451     /**
452      * Utility method to send an error response. If the builder is not committed, this call is
453      * equivalent to a setResponse, addcontent and complete call.
454      * 
455      * @param code
456      * @param reason
457      * @param content
458      * @param close
459      * @throws IOException
460      */
461     public void sendError(int code, String reason, String content, boolean close) throws IOException
462     {
463         if (close)
464             _close = close;
465         if (!isCommitted())
466         {
467             setResponse(code, reason);
468             completeHeader(null, false);
469             if (content != null) 
470                 addContent(new View(new ByteArrayBuffer(content)), Generator.LAST);
471             complete();
472         }
473     }
474 
475     /* ------------------------------------------------------------ */
476     /**
477      * @return Returns the contentWritten.
478      */
479     public long getContentWritten()
480     {
481         return _contentWritten;
482     }
483 
484 
485     /* ------------------------------------------------------------ */
486     /* ------------------------------------------------------------ */
487     /* ------------------------------------------------------------ */
488     /* ------------------------------------------------------------ */
489     /** Output.
490      * 
491      * <p>
492      * Implements  {@link javax.servlet.ServletOutputStream} from the {@link javax.servlet} package.   
493      * </p>
494      * A {@link ServletOutputStream} implementation that writes content
495      * to a {@link AbstractGenerator}.   The class is designed to be reused
496      * and can be reopened after a close.
497      */
498     public static class Output extends ServletOutputStream 
499     {
500         protected AbstractGenerator _generator;
501         protected long _maxIdleTime;
502         protected ByteArrayBuffer _buf = new ByteArrayBuffer(NO_BYTES);
503         protected boolean _closed;
504         
505         // These are held here for reuse by Writer
506         String _characterEncoding;
507         Writer _converter;
508         char[] _chars;
509         ByteArrayOutputStream2 _bytes;
510         
511 
512         /* ------------------------------------------------------------ */
513         public Output(AbstractGenerator generator, long maxIdleTime)
514         {
515             _generator=generator;
516             _maxIdleTime=maxIdleTime;
517         }
518         
519         /* ------------------------------------------------------------ */
520         /*
521          * @see java.io.OutputStream#close()
522          */
523         public void close() throws IOException
524         {
525             _closed=true;
526         }
527 
528         /* ------------------------------------------------------------ */
529         void  blockForOutput() throws IOException
530         {
531             if (_generator._endp.isBlocking())
532             {
533                 try
534                 {
535                     flush();
536                 }
537                 catch(IOException e)
538                 {
539                     _generator._endp.close();
540                     throw e;
541                 }
542             }
543             else
544             {
545                 if (!_generator._endp.blockWritable(_maxIdleTime))
546                 {
547                     _generator._endp.close();
548                     throw new EofException("timeout");
549                 }
550                 
551                 _generator.flush();
552             }
553         }
554         
555         /* ------------------------------------------------------------ */
556         void reopen()
557         {
558             _closed=false;
559         }
560         
561         /* ------------------------------------------------------------ */
562         public void flush() throws IOException
563         {
564             // block until everything is flushed
565             Buffer content = _generator._content;
566             Buffer buffer = _generator._buffer;
567             if (content!=null && content.length()>0 || buffer!=null && buffer.length()>0 || _generator.isBufferFull())
568             {
569                 _generator.flush();
570                 
571                 while ((content!=null && content.length()>0 ||buffer!=null && buffer.length()>0) && _generator._endp.isOpen())
572                     blockForOutput();
573             }
574         }
575 
576         /* ------------------------------------------------------------ */
577         public void write(byte[] b, int off, int len) throws IOException
578         {
579             _buf.wrap(b, off, len);
580             write(_buf);
581             _buf.wrap(NO_BYTES);
582         }
583 
584         /* ------------------------------------------------------------ */
585         /*
586          * @see java.io.OutputStream#write(byte[])
587          */
588         public void write(byte[] b) throws IOException
589         {
590             _buf.wrap(b);
591             write(_buf);
592             _buf.wrap(NO_BYTES);
593         }
594 
595         /* ------------------------------------------------------------ */
596         /*
597          * @see java.io.OutputStream#write(int)
598          */
599         public void write(int b) throws IOException
600         {
601             if (_closed)
602                 throw new IOException("Closed");
603             if (!_generator._endp.isOpen())
604                 throw new EofException();
605             
606             // Block until we can add _content.
607             while (_generator.isBufferFull())
608             {
609                 blockForOutput();
610                 if (_closed)
611                     throw new IOException("Closed");
612                 if (!_generator._endp.isOpen())
613                     throw new EofException();
614             }
615 
616             // Add the _content
617             if (_generator.addContent((byte)b))
618                 // Buffers are full so flush.
619                 flush();
620            
621             if (_generator.isContentWritten())
622             {
623                 flush();
624                 close();
625             }
626         }
627 
628         /* ------------------------------------------------------------ */
629         private void write(Buffer buffer) throws IOException
630         {
631             if (_closed)
632                 throw new IOException("Closed");
633             if (!_generator._endp.isOpen())
634                 throw new EofException();
635             
636             // Block until we can add _content.
637             while (_generator.isBufferFull())
638             {
639                 blockForOutput();
640                 if (_closed)
641                     throw new IOException("Closed");
642                 if (!_generator._endp.isOpen())
643                     throw new EofException();
644             }
645 
646             // Add the _content
647             _generator.addContent(buffer, Generator.MORE);
648 
649             // Have to flush and complete headers?
650             if (_generator.isBufferFull())
651                 flush();
652             
653             if (_generator.isContentWritten())
654             {
655                 flush();
656                 close();
657             }
658 
659             // Block until our buffer is free
660             while (buffer.length() > 0 && _generator._endp.isOpen())
661                 blockForOutput();
662         }
663 
664         /* ------------------------------------------------------------ */
665         /* 
666          * @see javax.servlet.ServletOutputStream#print(java.lang.String)
667          */
668         public void print(String s) throws IOException
669         {
670             write(s.getBytes());
671         }
672     }
673     
674     /* ------------------------------------------------------------ */
675     /* ------------------------------------------------------------ */
676     /* ------------------------------------------------------------ */
677     /** OutputWriter.
678      * A writer that can wrap a {@link Output} stream and provide
679      * character encodings.
680      *
681      * The UTF-8 encoding is done by this class and no additional 
682      * buffers or Writers are used.
683      * The UTF-8 code was inspired by http://javolution.org
684      */
685     public static class OutputWriter extends Writer
686     {
687         private static final int WRITE_CONV = 0;
688         private static final int WRITE_ISO1 = 1;
689         private static final int WRITE_UTF8 = 2;
690         
691         Output _out;
692         AbstractGenerator _generator;
693         int _writeMode;
694         int _surrogate;
695 
696         /* ------------------------------------------------------------ */
697         public OutputWriter(Output out)
698         {
699             _out=out;
700             _generator=_out._generator;
701              
702         }
703 
704         /* ------------------------------------------------------------ */
705         public void setCharacterEncoding(String encoding)
706         {
707             if (encoding == null || StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding))
708             {
709                 _writeMode = WRITE_ISO1;
710             }
711             else if (StringUtil.__UTF8.equalsIgnoreCase(encoding))
712             {
713                 _writeMode = WRITE_UTF8;
714             }
715             else
716             {
717                 _writeMode = WRITE_CONV;
718                 if (_out._characterEncoding == null || !_out._characterEncoding.equalsIgnoreCase(encoding))
719                     _out._converter = null; // Set lazily in getConverter()
720             }
721             
722             _out._characterEncoding = encoding;
723             if (_out._bytes==null)
724                 _out._bytes = new ByteArrayOutputStream2(MAX_OUTPUT_CHARS);
725         }
726 
727         /* ------------------------------------------------------------ */
728         public void close() throws IOException
729         {
730             _out.close();
731         }
732 
733         /* ------------------------------------------------------------ */
734         public void flush() throws IOException
735         {
736             _out.flush();
737         }
738 
739         /* ------------------------------------------------------------ */
740         public void write (String s,int offset, int length) throws IOException
741         {   
742             while (length > MAX_OUTPUT_CHARS)
743             {
744                 write(s, offset, MAX_OUTPUT_CHARS);
745                 offset += MAX_OUTPUT_CHARS;
746                 length -= MAX_OUTPUT_CHARS;
747             }
748 
749             if (_out._chars==null)
750             {
751                 _out._chars = new char[MAX_OUTPUT_CHARS]; 
752             }
753             char[] chars = _out._chars;
754             s.getChars(offset, offset + length, chars, 0);
755             write(chars, 0, length);
756         }
757 
758         /* ------------------------------------------------------------ */
759         public void write (char[] s,int offset, int length) throws IOException
760         {              
761             Output out = _out; 
762             
763             while (length > 0)
764             {  
765                 out._bytes.reset();
766                 int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
767 
768                 switch (_writeMode)
769                 {
770                     case WRITE_CONV:
771                     {
772                         Writer converter=getConverter();
773                         converter.write(s, offset, chars);
774                         converter.flush();
775                     }
776                     break;
777 
778                     case WRITE_ISO1:
779                     {
780                         byte[] buffer=out._bytes.getBuf();
781                         int bytes=out._bytes.getCount();
782                         
783                         if (chars>buffer.length-bytes)
784                             chars=buffer.length-bytes;
785 
786                         for (int i = 0; i < chars; i++)
787                         {
788                             int c = s[offset+i];
789                             buffer[bytes++]=(byte)(c<256?c:'?'); // ISO-1 and UTF-8 match for 0 - 255
790                         }
791                         if (bytes>=0)
792                             out._bytes.setCount(bytes);
793 
794                         break;
795                     }
796 
797                     case WRITE_UTF8:
798                     {
799                         byte[] buffer=out._bytes.getBuf();
800                         int bytes=out._bytes.getCount();
801          
802                         if (bytes+chars>buffer.length)
803                             chars=buffer.length-bytes;
804                         
805                         for (int i = 0; i < chars; i++)
806                         {
807                             int code = s[offset+i];
808 
809                             if ((code & 0xffffff80) == 0) 
810                             {
811                                 // 1b
812                                 if (bytes+1>buffer.length)
813                                 {
814                                     chars=i;
815                                     break;
816                                 }
817                                 buffer[bytes++]=(byte)(code);
818                             }
819                             else
820 			    {
821 				if((code&0xfffff800)==0)
822 				{
823 				    // 2b
824 				    if (bytes+2>buffer.length)
825 				    {
826 					chars=i;
827 					break;
828 				    }
829 				    buffer[bytes++]=(byte)(0xc0|(code>>6));
830 				    buffer[bytes++]=(byte)(0x80|(code&0x3f));
831 				}
832 				else if((code&0xffff0000)==0)
833 				{
834 				    // 3b
835 				    if (bytes+3>buffer.length)
836 				    {
837 					chars=i;
838 					break;
839 				    }
840 				    buffer[bytes++]=(byte)(0xe0|(code>>12));
841 				    buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
842 				    buffer[bytes++]=(byte)(0x80|(code&0x3f));
843 				}
844 				else if((code&0xff200000)==0)
845 				{
846 				    // 4b
847 				    if (bytes+4>buffer.length)
848 				    {
849 					chars=i;
850 					break;
851 				    }
852 				    buffer[bytes++]=(byte)(0xf0|(code>>18));
853 				    buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
854 				    buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
855 				    buffer[bytes++]=(byte)(0x80|(code&0x3f));
856 				}
857 				else if((code&0xf4000000)==0)
858 				{
859 				    // 5b
860 				    if (bytes+5>buffer.length)
861 				    {
862 					chars=i;
863 					break;
864 				    }
865 				    buffer[bytes++]=(byte)(0xf8|(code>>24));
866 				    buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
867 				    buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
868 				    buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
869 				    buffer[bytes++]=(byte)(0x80|(code&0x3f));
870 				}
871 				else if((code&0x80000000)==0)
872 				{
873 				    // 6b
874 				    if (bytes+6>buffer.length)
875 				    {
876 					chars=i;
877 					break;
878 				    }
879 				    buffer[bytes++]=(byte)(0xfc|(code>>30));
880 				    buffer[bytes++]=(byte)(0x80|((code>>24)&0x3f));
881 				    buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
882 				    buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
883 				    buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
884 				    buffer[bytes++]=(byte)(0x80|(code&0x3f));
885 				}
886 				else
887 				{
888 				    buffer[bytes++]=(byte)('?');
889 				}
890 				if (bytes==buffer.length)
891 				{
892 				    chars=i+1;
893 				    break;
894 				}
895 			    }
896                         }
897                         out._bytes.setCount(bytes);
898                         break;
899                     }
900                     default:
901                         throw new IllegalStateException();
902                 }
903                 
904                 out._bytes.writeTo(out);
905                 length-=chars;
906                 offset+=chars;
907             }
908         }
909 
910         /* ------------------------------------------------------------ */
911         private Writer getConverter() throws IOException
912         {
913             if (_out._converter == null)
914                 _out._converter = new OutputStreamWriter(_out._bytes, _out._characterEncoding);
915             return _out._converter;
916         }   
917     }
918 }