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         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         }
581 
582         /* ------------------------------------------------------------ */
583         /*
584          * @see java.io.OutputStream#write(byte[])
585          */
586         public void write(byte[] b) throws IOException
587         {
588             _buf.wrap(b);
589             write(_buf);
590         }
591 
592         /* ------------------------------------------------------------ */
593         /*
594          * @see java.io.OutputStream#write(int)
595          */
596         public void write(int b) throws IOException
597         {
598             if (_closed)
599                 throw new IOException("Closed");
600             if (!_generator._endp.isOpen())
601                 throw new EofException();
602             
603             // Block until we can add _content.
604             while (_generator.isBufferFull())
605             {
606                 blockForOutput();
607                 if (_closed)
608                     throw new IOException("Closed");
609                 if (!_generator._endp.isOpen())
610                     throw new EofException();
611             }
612 
613             // Add the _content
614             if (_generator.addContent((byte)b))
615                 // Buffers are full so flush.
616                 flush();
617            
618             if (_generator.isContentWritten())
619             {
620                 flush();
621                 close();
622             }
623         }
624 
625         /* ------------------------------------------------------------ */
626         private void write(Buffer buffer) throws IOException
627         {
628             if (_closed)
629                 throw new IOException("Closed");
630             if (!_generator._endp.isOpen())
631                 throw new EofException();
632             
633             // Block until we can add _content.
634             while (_generator.isBufferFull())
635             {
636                 blockForOutput();
637                 if (_closed)
638                     throw new IOException("Closed");
639                 if (!_generator._endp.isOpen())
640                     throw new EofException();
641             }
642 
643             // Add the _content
644             _generator.addContent(buffer, Generator.MORE);
645 
646             // Have to flush and complete headers?
647             if (_generator.isBufferFull())
648                 flush();
649             
650             if (_generator.isContentWritten())
651             {
652                 flush();
653                 close();
654             }
655 
656             // Block until our buffer is free
657             while (buffer.length() > 0 && _generator._endp.isOpen())
658                 blockForOutput();
659         }
660 
661         /* ------------------------------------------------------------ */
662         /* 
663          * @see javax.servlet.ServletOutputStream#print(java.lang.String)
664          */
665         public void print(String s) throws IOException
666         {
667             write(s.getBytes());
668         }
669     }
670     
671     /* ------------------------------------------------------------ */
672     /* ------------------------------------------------------------ */
673     /* ------------------------------------------------------------ */
674     /** OutputWriter.
675      * A writer that can wrap a {@link Output} stream and provide
676      * character encodings.
677      *
678      * The UTF-8 encoding is done by this class and no additional 
679      * buffers or Writers are used.
680      * The UTF-8 code was inspired by http://javolution.org
681      */
682     public static class OutputWriter extends Writer
683     {
684         private static final int WRITE_CONV = 0;
685         private static final int WRITE_ISO1 = 1;
686         private static final int WRITE_UTF8 = 2;
687         
688         Output _out;
689         AbstractGenerator _generator;
690         int _writeMode;
691         int _surrogate;
692 
693         /* ------------------------------------------------------------ */
694         public OutputWriter(Output out)
695         {
696             _out=out;
697             _generator=_out._generator;
698              
699         }
700 
701         /* ------------------------------------------------------------ */
702         public void setCharacterEncoding(String encoding)
703         {
704             if (encoding == null || StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding))
705             {
706                 _writeMode = WRITE_ISO1;
707             }
708             else if (StringUtil.__UTF8.equalsIgnoreCase(encoding))
709             {
710                 _writeMode = WRITE_UTF8;
711             }
712             else
713             {
714                 _writeMode = WRITE_CONV;
715                 if (_out._characterEncoding == null || !_out._characterEncoding.equalsIgnoreCase(encoding))
716                     _out._converter = null; // Set lazily in getConverter()
717             }
718             
719             _out._characterEncoding = encoding;
720             if (_out._bytes==null)
721                 _out._bytes = new ByteArrayOutputStream2(MAX_OUTPUT_CHARS);
722         }
723 
724         /* ------------------------------------------------------------ */
725         public void close() throws IOException
726         {
727             _out.close();
728         }
729 
730         /* ------------------------------------------------------------ */
731         public void flush() throws IOException
732         {
733             _out.flush();
734         }
735 
736         /* ------------------------------------------------------------ */
737         public void write (String s,int offset, int length) throws IOException
738         {   
739             while (length > MAX_OUTPUT_CHARS)
740             {
741                 write(s, offset, MAX_OUTPUT_CHARS);
742                 offset += MAX_OUTPUT_CHARS;
743                 length -= MAX_OUTPUT_CHARS;
744             }
745 
746             if (_out._chars==null)
747             {
748                 _out._chars = new char[MAX_OUTPUT_CHARS]; 
749             }
750             char[] chars = _out._chars;
751             s.getChars(offset, offset + length, chars, 0);
752             write(chars, 0, length);
753         }
754 
755         /* ------------------------------------------------------------ */
756         public void write (char[] s,int offset, int length) throws IOException
757         {              
758             Output out = _out; 
759             
760             while (length > 0)
761             {  
762                 out._bytes.reset();
763                 int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
764 
765                 switch (_writeMode)
766                 {
767                     case WRITE_CONV:
768                     {
769                         Writer converter=getConverter();
770                         converter.write(s, offset, chars);
771                         converter.flush();
772                     }
773                     break;
774 
775                     case WRITE_ISO1:
776                     {
777                         byte[] buffer=out._bytes.getBuf();
778                         int bytes=out._bytes.getCount();
779                         
780                         if (chars>buffer.length-bytes)
781                             chars=buffer.length-bytes;
782 
783                         for (int i = 0; i < chars; i++)
784                         {
785                             int c = s[offset+i];
786                             buffer[bytes++]=(byte)(c<256?c:'?'); // ISO-1 and UTF-8 match for 0 - 255
787                         }
788                         if (bytes>=0)
789                             out._bytes.setCount(bytes);
790 
791                         break;
792                     }
793 
794                     case WRITE_UTF8:
795                     {
796                         byte[] buffer=out._bytes.getBuf();
797                         int bytes=out._bytes.getCount();
798          
799                         if (bytes+chars>buffer.length)
800                             chars=buffer.length-bytes;
801                         
802                         for (int i = 0; i < chars; i++)
803                         {
804                             int code = s[offset+i];
805 
806                             if ((code & 0xffffff80) == 0) 
807                             {
808                                 // 1b
809                                 buffer[bytes++]=(byte)(code);
810                             }
811                             else if((code&0xfffff800)==0)
812                             {
813                                 // 2b
814                                 if (bytes+2>buffer.length)
815                                 {
816                                     chars=i;
817                                     break;
818                                 }
819                                 buffer[bytes++]=(byte)(0xc0|(code>>6));
820                                 buffer[bytes++]=(byte)(0x80|(code&0x3f));
821 
822                                 if (bytes+chars-i-1>buffer.length)
823                                     chars-=1;
824                             }
825                             else if((code&0xffff0000)==0)
826                             {
827                                 // 3b
828                                 if (bytes+3>buffer.length)
829                                 {
830                                     chars=i;
831                                     break;
832                                 }
833                                 buffer[bytes++]=(byte)(0xe0|(code>>12));
834                                 buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
835                                 buffer[bytes++]=(byte)(0x80|(code&0x3f));
836 
837                                 if (bytes+chars-i-1>buffer.length)
838                                     chars-=2;
839                             }
840                             else if((code&0xff200000)==0)
841                             {
842                                 // 4b
843                                 if (bytes+4>buffer.length)
844                                 {
845                                     chars=i;
846                                     break;
847                                 }
848                                 buffer[bytes++]=(byte)(0xf0|(code>>18));
849                                 buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
850                                 buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
851                                 buffer[bytes++]=(byte)(0x80|(code&0x3f));
852 
853                                 if (bytes+chars-i-1>buffer.length)
854                                     chars-=3;
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                                 if (bytes+chars-i-1>buffer.length)
871                                     chars-=4;
872                             }
873                             else if((code&0x80000000)==0)
874                             {
875                                 // 6b
876                                 if (bytes+6>buffer.length)
877                                 {
878                                     chars=i;
879                                     break;
880                                 }
881                                 buffer[bytes++]=(byte)(0xfc|(code>>30));
882                                 buffer[bytes++]=(byte)(0x80|((code>>24)&0x3f));
883                                 buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
884                                 buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
885                                 buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
886                                 buffer[bytes++]=(byte)(0x80|(code&0x3f));
887 
888                                 if (bytes+chars-i-1>buffer.length)
889                                     chars-=5;
890                             }
891                             else
892                             {
893                                 buffer[bytes++]=(byte)('?');
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 }