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 (!isCommitted())
464         {
465             setResponse(code, reason);
466             _close = close;
467             completeHeader(null, false);
468             if (content != null) 
469                 addContent(new View(new ByteArrayBuffer(content)), Generator.LAST);
470             complete();
471         }
472     }
473 
474     /* ------------------------------------------------------------ */
475     /**
476      * @return Returns the contentWritten.
477      */
478     public long getContentWritten()
479     {
480         return _contentWritten;
481     }
482 
483 
484     /* ------------------------------------------------------------ */
485     /* ------------------------------------------------------------ */
486     /* ------------------------------------------------------------ */
487     /* ------------------------------------------------------------ */
488     /** Output.
489      * 
490      * <p>
491      * Implements  {@link javax.servlet.ServletOutputStream} from the {@link javax.servlet} package.   
492      * </p>
493      * A {@link ServletOutputStream} implementation that writes content
494      * to a {@link AbstractGenerator}.   The class is designed to be reused
495      * and can be reopened after a close.
496      */
497     public static class Output extends ServletOutputStream 
498     {
499         protected AbstractGenerator _generator;
500         protected long _maxIdleTime;
501         protected ByteArrayBuffer _buf = new ByteArrayBuffer(NO_BYTES);
502         protected boolean _closed;
503         
504         // These are held here for reuse by Writer
505         String _characterEncoding;
506         Writer _converter;
507         char[] _chars;
508         ByteArrayOutputStream2 _bytes;
509         
510 
511         /* ------------------------------------------------------------ */
512         public Output(AbstractGenerator generator, long maxIdleTime)
513         {
514             _generator=generator;
515             _maxIdleTime=maxIdleTime;
516         }
517         
518         /* ------------------------------------------------------------ */
519         /*
520          * @see java.io.OutputStream#close()
521          */
522         public void close() throws IOException
523         {
524             _closed=true;
525         }
526 
527         /* ------------------------------------------------------------ */
528         void  blockForOutput() throws IOException
529         {
530             if (_generator._endp.isBlocking())
531             {
532                 try
533                 {
534                     flush();
535                 }
536                 catch(IOException e)
537                 {
538                     _generator._endp.close();
539                     throw e;
540                 }
541             }
542             else
543             {
544                 if (!_generator._endp.blockWritable(_maxIdleTime))
545                 {
546                     _generator._endp.close();
547                     throw new EofException("timeout");
548                 }
549                 
550                 _generator.flush();
551             }
552         }
553         
554         /* ------------------------------------------------------------ */
555         void reopen()
556         {
557             _closed=false;
558         }
559         
560         /* ------------------------------------------------------------ */
561         public void flush() throws IOException
562         {
563             // block until everything is flushed
564             Buffer content = _generator._content;
565             Buffer buffer = _generator._buffer;
566             if (content!=null && content.length()>0 || buffer!=null && buffer.length()>0 || _generator.isBufferFull())
567             {
568                 _generator.flush();
569                 
570                 while ((content!=null && content.length()>0 ||buffer!=null && buffer.length()>0) && _generator._endp.isOpen())
571                     blockForOutput();
572             }
573         }
574 
575         /* ------------------------------------------------------------ */
576         public void write(byte[] b, int off, int len) throws IOException
577         {
578             _buf.wrap(b, off, len);
579             write(_buf);
580             _buf.wrap(NO_BYTES);
581         }
582 
583         /* ------------------------------------------------------------ */
584         /*
585          * @see java.io.OutputStream#write(byte[])
586          */
587         public void write(byte[] b) throws IOException
588         {
589             _buf.wrap(b);
590             write(_buf);
591             _buf.wrap(NO_BYTES);
592         }
593 
594         /* ------------------------------------------------------------ */
595         /*
596          * @see java.io.OutputStream#write(int)
597          */
598         public void write(int b) throws IOException
599         {
600             if (_closed)
601                 throw new IOException("Closed");
602             if (!_generator._endp.isOpen())
603                 throw new EofException();
604             
605             // Block until we can add _content.
606             while (_generator.isBufferFull())
607             {
608                 blockForOutput();
609                 if (_closed)
610                     throw new IOException("Closed");
611                 if (!_generator._endp.isOpen())
612                     throw new EofException();
613             }
614 
615             // Add the _content
616             if (_generator.addContent((byte)b))
617                 // Buffers are full so flush.
618                 flush();
619            
620             if (_generator.isContentWritten())
621             {
622                 flush();
623                 close();
624             }
625         }
626 
627         /* ------------------------------------------------------------ */
628         private void write(Buffer buffer) throws IOException
629         {
630             if (_closed)
631                 throw new IOException("Closed");
632             if (!_generator._endp.isOpen())
633                 throw new EofException();
634             
635             // Block until we can add _content.
636             while (_generator.isBufferFull())
637             {
638                 blockForOutput();
639                 if (_closed)
640                     throw new IOException("Closed");
641                 if (!_generator._endp.isOpen())
642                     throw new EofException();
643             }
644 
645             // Add the _content
646             _generator.addContent(buffer, Generator.MORE);
647 
648             // Have to flush and complete headers?
649             if (_generator.isBufferFull())
650                 flush();
651             
652             if (_generator.isContentWritten())
653             {
654                 flush();
655                 close();
656             }
657 
658             // Block until our buffer is free
659             while (buffer.length() > 0 && _generator._endp.isOpen())
660                 blockForOutput();
661         }
662 
663         /* ------------------------------------------------------------ */
664         /* 
665          * @see javax.servlet.ServletOutputStream#print(java.lang.String)
666          */
667         public void print(String s) throws IOException
668         {
669             write(s.getBytes());
670         }
671     }
672     
673     /* ------------------------------------------------------------ */
674     /* ------------------------------------------------------------ */
675     /* ------------------------------------------------------------ */
676     /** OutputWriter.
677      * A writer that can wrap a {@link Output} stream and provide
678      * character encodings.
679      *
680      * The UTF-8 encoding is done by this class and no additional 
681      * buffers or Writers are used.
682      * The UTF-8 code was inspired by http://javolution.org
683      */
684     public static class OutputWriter extends Writer
685     {
686         private static final int WRITE_CONV = 0;
687         private static final int WRITE_ISO1 = 1;
688         private static final int WRITE_UTF8 = 2;
689         
690         Output _out;
691         AbstractGenerator _generator;
692         int _writeMode;
693         int _surrogate;
694 
695         /* ------------------------------------------------------------ */
696         public OutputWriter(Output out)
697         {
698             _out=out;
699             _generator=_out._generator;
700              
701         }
702 
703         /* ------------------------------------------------------------ */
704         public void setCharacterEncoding(String encoding)
705         {
706             if (encoding == null || StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding))
707             {
708                 _writeMode = WRITE_ISO1;
709             }
710             else if (StringUtil.__UTF8.equalsIgnoreCase(encoding))
711             {
712                 _writeMode = WRITE_UTF8;
713             }
714             else
715             {
716                 _writeMode = WRITE_CONV;
717                 if (_out._characterEncoding == null || !_out._characterEncoding.equalsIgnoreCase(encoding))
718                     _out._converter = null; // Set lazily in getConverter()
719             }
720             
721             _out._characterEncoding = encoding;
722             if (_out._bytes==null)
723                 _out._bytes = new ByteArrayOutputStream2(MAX_OUTPUT_CHARS);
724         }
725 
726         /* ------------------------------------------------------------ */
727         public void close() throws IOException
728         {
729             _out.close();
730         }
731 
732         /* ------------------------------------------------------------ */
733         public void flush() throws IOException
734         {
735             _out.flush();
736         }
737 
738         /* ------------------------------------------------------------ */
739         public void write (String s,int offset, int length) throws IOException
740         {   
741             while (length > MAX_OUTPUT_CHARS)
742             {
743                 write(s, offset, MAX_OUTPUT_CHARS);
744                 offset += MAX_OUTPUT_CHARS;
745                 length -= MAX_OUTPUT_CHARS;
746             }
747 
748             if (_out._chars==null)
749             {
750                 _out._chars = new char[MAX_OUTPUT_CHARS]; 
751             }
752             char[] chars = _out._chars;
753             s.getChars(offset, offset + length, chars, 0);
754             write(chars, 0, length);
755         }
756 
757         /* ------------------------------------------------------------ */
758         public void write (char[] s,int offset, int length) throws IOException
759         {              
760             Output out = _out; 
761             
762             while (length > 0)
763             {  
764                 out._bytes.reset();
765                 int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
766 
767                 switch (_writeMode)
768                 {
769                     case WRITE_CONV:
770                     {
771                         Writer converter=getConverter();
772                         converter.write(s, offset, chars);
773                         converter.flush();
774                     }
775                     break;
776 
777                     case WRITE_ISO1:
778                     {
779                         byte[] buffer=out._bytes.getBuf();
780                         int bytes=out._bytes.getCount();
781                         
782                         if (chars>buffer.length-bytes)
783                             chars=buffer.length-bytes;
784 
785                         for (int i = 0; i < chars; i++)
786                         {
787                             int c = s[offset+i];
788                             buffer[bytes++]=(byte)(c<256?c:'?'); // ISO-1 and UTF-8 match for 0 - 255
789                         }
790                         if (bytes>=0)
791                             out._bytes.setCount(bytes);
792 
793                         break;
794                     }
795 
796                     case WRITE_UTF8:
797                     {
798                         byte[] buffer=out._bytes.getBuf();
799                         int bytes=out._bytes.getCount();
800          
801                         if (bytes+chars>buffer.length)
802                             chars=buffer.length-bytes;
803                         
804                         for (int i = 0; i < chars; i++)
805                         {
806                             int code = s[offset+i];
807 
808                             if ((code & 0xffffff80) == 0) 
809                             {
810                                 // 1b
811                                 if (bytes+1>buffer.length)
812                                 {
813                                     chars=i;
814                                     break;
815                                 }
816                                 buffer[bytes++]=(byte)(code);
817                             }
818                             else
819 			    {
820 				if((code&0xfffff800)==0)
821 				{
822 				    // 2b
823 				    if (bytes+2>buffer.length)
824 				    {
825 					chars=i;
826 					break;
827 				    }
828 				    buffer[bytes++]=(byte)(0xc0|(code>>6));
829 				    buffer[bytes++]=(byte)(0x80|(code&0x3f));
830 				}
831 				else if((code&0xffff0000)==0)
832 				{
833 				    // 3b
834 				    if (bytes+3>buffer.length)
835 				    {
836 					chars=i;
837 					break;
838 				    }
839 				    buffer[bytes++]=(byte)(0xe0|(code>>12));
840 				    buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
841 				    buffer[bytes++]=(byte)(0x80|(code&0x3f));
842 				}
843 				else if((code&0xff200000)==0)
844 				{
845 				    // 4b
846 				    if (bytes+4>buffer.length)
847 				    {
848 					chars=i;
849 					break;
850 				    }
851 				    buffer[bytes++]=(byte)(0xf0|(code>>18));
852 				    buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
853 				    buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
854 				    buffer[bytes++]=(byte)(0x80|(code&0x3f));
855 				}
856 				else if((code&0xf4000000)==0)
857 				{
858 				    // 5b
859 				    if (bytes+5>buffer.length)
860 				    {
861 					chars=i;
862 					break;
863 				    }
864 				    buffer[bytes++]=(byte)(0xf8|(code>>24));
865 				    buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
866 				    buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
867 				    buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
868 				    buffer[bytes++]=(byte)(0x80|(code&0x3f));
869 				}
870 				else if((code&0x80000000)==0)
871 				{
872 				    // 6b
873 				    if (bytes+6>buffer.length)
874 				    {
875 					chars=i;
876 					break;
877 				    }
878 				    buffer[bytes++]=(byte)(0xfc|(code>>30));
879 				    buffer[bytes++]=(byte)(0x80|((code>>24)&0x3f));
880 				    buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
881 				    buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
882 				    buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
883 				    buffer[bytes++]=(byte)(0x80|(code&0x3f));
884 				}
885 				else
886 				{
887 				    buffer[bytes++]=(byte)('?');
888 				}
889 				if (bytes==buffer.length)
890 				{
891 				    chars=i+1;
892 				    break;
893 				}
894 			    }
895                         }
896                         out._bytes.setCount(bytes);
897                         break;
898                     }
899                     default:
900                         throw new IllegalStateException();
901                 }
902                 
903                 out._bytes.writeTo(out);
904                 length-=chars;
905                 offset+=chars;
906             }
907         }
908 
909         /* ------------------------------------------------------------ */
910         private Writer getConverter() throws IOException
911         {
912             if (_out._converter == null)
913                 _out._converter = new OutputStreamWriter(_out._bytes, _out._characterEncoding);
914             return _out._converter;
915         }   
916     }
917 }