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 }