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