View Javadoc

    1   //========================================================================
    2   //$Id: HttpGenerator.java,v 1.7 2005/11/25 21:17:12 gregwilkins Exp $
    3   //Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
    4   //------------------------------------------------------------------------
    5   //Licensed under the Apache License, Version 2.0 (the "License");
    6   //you may not use this file except in compliance with the License.
    7   //You may obtain a copy of the License at
    8   //http://www.apache.org/licenses/LICENSE-2.0
    9   //Unless required by applicable law or agreed to in writing, software
    10  //distributed under the License is distributed on an "AS IS" BASIS,
    11  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //See the License for the specific language governing permissions and
    13  //limitations under the License.
    14  //========================================================================
    15
    16  package org.mortbay.jetty;
    17
    18  import java.io.IOException;
    19  import java.io.OutputStreamWriter;
    20  import java.io.Writer;
    21  import java.lang.reflect.Field;
    22  import java.lang.reflect.Modifier;
    23
    24  import javax.servlet.ServletOutputStream;
    25  import javax.servlet.http.HttpServletResponse;
    26
    27  import org.mortbay.io.Buffer;
    28  import org.mortbay.io.Buffers;
    29  import org.mortbay.io.ByteArrayBuffer;
    30  import org.mortbay.io.EndPoint;
    31  import org.mortbay.io.View;
    32  import org.mortbay.log.Log;
    33  import org.mortbay.util.ByteArrayOutputStream2;
    34  import org.mortbay.util.StringUtil;
    35  import org.mortbay.util.TypeUtil;
    36
    37  /* ------------------------------------------------------------ */
    38  /**
    39   * Abstract Generator. Builds HTTP Messages.
    40   *
    41   * Currently this class uses a system parameter "jetty.direct.writers" to control
    42   * two optional writer to byte conversions. buffer.writers=true will probably be
    43   * faster, but will consume more memory.   This option is just for testing and tuning.
    44   *
    45   * @author gregw
    46   *
    47   */
    48  public abstract class AbstractGenerator implements Generator
    49  {
    50      // states
    51      public final static int STATE_HEADER = 0;
    52      public final static int STATE_CONTENT = 2;
    53      public final static int STATE_FLUSHING = 3;
    54      public final static int STATE_END = 4;
    55
    56      private static final byte[] NO_BYTES = {};
    57      private static int MAX_OUTPUT_CHARS = 512;
    58
    59      private static Buffer[] __reasons = new Buffer[505];
    60      static
    61      {
    62          Field[] fields = HttpServletResponse.class.getDeclaredFields();
    63          for (int i=0;i<fields.length;i++)
    64          {
    65              if ((fields[i].getModifiers()&Modifier.STATIC)!=0 &&
    66                   fields[i].getName().startsWith("SC_"))
    67              {
    68                  try
    69                  {
    70                      int code = fields[i].getInt(null);
    71                      if (code<__reasons.length)
    72                          __reasons[code]=new ByteArrayBuffer(fields[i].getName().substring(3));
    73                  }
    74                  catch(IllegalAccessException e)
    75                  {}
    76              }
    77          }
    78      }
    79
    80      protected static Buffer getReasonBuffer(int code)
    81      {
    82          Buffer reason=(code<__reasons.length)?__reasons[code]:null;
    83          return reason==null?null:reason;
    84      }
    85
    86      public static String getReason(int code)
    87      {
    88          Buffer reason=(code<__reasons.length)?__reasons[code]:null;
    89          return reason==null?TypeUtil.toString(code):reason.toString();
    90      }
    91
    92      // data
    93      protected int _state = STATE_HEADER;
    94
    95      protected int _status = 0;
    96      protected int _version = HttpVersions.HTTP_1_1_ORDINAL;
    97      protected  Buffer _reason;
    98      protected  Buffer _method;
    99      protected  String _uri;
    100
    101     protected long _contentWritten = 0;
    102     protected long _contentLength = HttpTokens.UNKNOWN_CONTENT;
    103     protected boolean _last = false;
    104     protected boolean _head = false;
    105     protected boolean _noContent = false;
    106     protected boolean _close = false;
    107
    108     protected Buffers _buffers; // source of buffers
    109     protected EndPoint _endp;
    110
    111     protected int _headerBufferSize;
    112     protected int _contentBufferSize;
    113
    114     protected Buffer _header; // Buffer for HTTP header (and maybe small _content)
    115     protected Buffer _buffer; // Buffer for copy of passed _content
    116     protected Buffer _content; // Buffer passed to addContent
    117
    118     private boolean _sendServerVersion;
    119
    120
    121     /* ------------------------------------------------------------------------------- */
    122     /**
    123      * Constructor.
    124      *
    125      * @param buffers buffer pool
    126      * @param headerBufferSize Size of the buffer to allocate for HTTP header
    127      * @param contentBufferSize Size of the buffer to allocate for HTTP content
    128      */
    129     public AbstractGenerator(Buffers buffers, EndPoint io, int headerBufferSize, int contentBufferSize)
    130     {
    131         this._buffers = buffers;
    132         this._endp = io;
    133         _headerBufferSize=headerBufferSize;
    134         _contentBufferSize=contentBufferSize;
    135     }
    136
    137     /* ------------------------------------------------------------------------------- */
    138     public void reset(boolean returnBuffers)
    139     {
    140         _state = STATE_HEADER;
    141         _status = 0;
    142         _version = HttpVersions.HTTP_1_1_ORDINAL;
    143         _reason = null;
    144         _last = false;
    145         _head = false;
    146         _noContent=false;
    147         _close = false;
    148         _contentWritten = 0;
    149         _contentLength = HttpTokens.UNKNOWN_CONTENT;
    150
    151         synchronized(this)
    152         {
    153             if (returnBuffers)
    154             {
    155                 if (_header != null)
    156                     _buffers.returnBuffer(_header);
    157                 _header = null;
    158                 if (_buffer != null)
    159                     _buffers.returnBuffer(_buffer);
    160                 _buffer = null;
    161             }
    162             else
    163             {
    164                 if (_header != null)
    165                     _header.clear();
    166
    167                 if (_buffer != null)
    168                 {
    169                     _buffers.returnBuffer(_buffer);
    170                     _buffer = null;
    171                 }
    172             }
    173         }
    174         _content = null;
    175         _method=null;
    176     }
    177
    178     /* ------------------------------------------------------------------------------- */
    179     public void resetBuffer()
    180     {
    181         if(_state>=STATE_FLUSHING)
    182             throw new IllegalStateException("Flushed");
    183
    184         _last = false;
    185         _close = false;
    186         _contentWritten = 0;
    187         _contentLength = HttpTokens.UNKNOWN_CONTENT;
    188         _content=null;
    189         if (_buffer!=null)
    190             _buffer.clear();
    191     }
    192
    193     /* ------------------------------------------------------------ */
    194     /**
    195      * @return Returns the contentBufferSize.
    196      */
    197     public int getContentBufferSize()
    198     {
    199         return _contentBufferSize;
    200     }
    201
    202     /* ------------------------------------------------------------ */
    203     /**
    204      * @param contentBufferSize The contentBufferSize to set.
    205      */
    206     public void increaseContentBufferSize(int contentBufferSize)
    207     {
    208         if (contentBufferSize > _contentBufferSize)
    209         {
    210             _contentBufferSize = contentBufferSize;
    211             if (_buffer != null)
    212             {
    213                 Buffer nb = _buffers.getBuffer(_contentBufferSize);
    214                 nb.put(_buffer);
    215                 _buffers.returnBuffer(_buffer);
    216                 _buffer = nb;
    217             }
    218         }
    219     }
    220
    221     /* ------------------------------------------------------------ */
    222     public Buffer getUncheckedBuffer()
    223     {
    224         return _buffer;
    225     }
    226
    227     /* ------------------------------------------------------------ */
    228     public boolean getSendServerVersion ()
    229     {
    230         return _sendServerVersion;
    231     }
    232
    233     /* ------------------------------------------------------------ */
    234     public void setSendServerVersion (boolean sendServerVersion)
    235     {
    236         _sendServerVersion = sendServerVersion;
    237     }
    238
    239     /* ------------------------------------------------------------ */
    240     public int getState()
    241     {
    242         return _state;
    243     }
    244
    245     /* ------------------------------------------------------------ */
    246     public boolean isState(int state)
    247     {
    248         return _state == state;
    249     }
    250
    251     /* ------------------------------------------------------------ */
    252     public boolean isComplete()
    253     {
    254         return _state == STATE_END;
    255     }
    256
    257     /* ------------------------------------------------------------ */
    258     public boolean isIdle()
    259     {
    260         return _state == STATE_HEADER && _method==null && _status==0;
    261     }
    262
    263     /* ------------------------------------------------------------ */
    264     public boolean isCommitted()
    265     {
    266         return _state != STATE_HEADER;
    267     }
    268
    269     /* ------------------------------------------------------------ */
    270     /**
    271      * @return Returns the head.
    272      */
    273     public boolean isHead()
    274     {
    275         return _head;
    276     }
    277
    278     /* ------------------------------------------------------------ */
    279     public void setContentLength(long value)
    280     {
    281         if (value<0)
    282             _contentLength=HttpTokens.UNKNOWN_CONTENT;
    283         else
    284             _contentLength=value;
    285     }
    286
    287     /* ------------------------------------------------------------ */
    288     /**
    289      * @param head The head to set.
    290      */
    291     public void setHead(boolean head)
    292     {
    293         _head = head;
    294     }
    295
    296     /* ------------------------------------------------------------ */
    297     /**
    298      * @return <code>false</code> if the connection should be closed after a request has been read,
    299      * <code>true</code> if it should be used for additional requests.
    300      */
    301     public boolean isPersistent()
    302     {
    303         return !_close;
    304     }
    305
    306     /* ------------------------------------------------------------ */
    307     public void setPersistent(boolean persistent)
    308     {
    309         _close=!persistent;
    310     }
    311
    312     /* ------------------------------------------------------------ */
    313     /**
    314      * @param version The version of the client the response is being sent to (NB. Not the version
    315      *            in the response, which is the version of the server).
    316      */
    317     public void setVersion(int version)
    318     {
    319         if (_state != STATE_HEADER) throw new IllegalStateException("STATE!=START");
    320         _version = version;
    321         if (_version==HttpVersions.HTTP_0_9_ORDINAL && _method!=null)
    322             _noContent=true;
    323     }
    324
    325     /* ------------------------------------------------------------ */
    326     public int getVersion()
    327     {
    328         return _version;
    329     }
    330
    331     /* ------------------------------------------------------------ */
    332     /**
    333      */
    334     public void setRequest(String method, String uri)
    335     {
    336         if (method==null || HttpMethods.GET.equals(method) )
    337             _method=HttpMethods.GET_BUFFER;
    338         else
    339             _method=HttpMethods.CACHE.lookup(method);
    340         _uri=uri;
    341         if (_version==HttpVersions.HTTP_0_9_ORDINAL)
    342             _noContent=true;
    343     }
    344
    345     /* ------------------------------------------------------------ */
    346     /**
    347      * @param status The status code to send.
    348      * @param reason the status message to send.
    349      */
    350     public void setResponse(int status, String reason)
    351     {
    352         if (_state != STATE_HEADER) throw new IllegalStateException("STATE!=START");
    353
    354         _status = status;
    355         if (reason!=null)
    356         {
    357             int len=reason.length();
    358             if (len>_headerBufferSize/2)
    359                 len=_headerBufferSize/2;
    360             _reason=new ByteArrayBuffer(len);
    361             for (int i=0;i<len;i++)
    362             {
    363                 char ch = reason.charAt(i);
    364                 if (ch!='\r'&&ch!='\n')
    365                     _reason.put((byte)ch);
    366                 else
    367                     _reason.put((byte)' ');
    368             }
    369         }
    370     }
    371
    372     /* ------------------------------------------------------------ */
    373     /** Prepare buffer for unchecked writes.
    374      * Prepare the generator buffer to receive unchecked writes
    375      * @return the available space in the buffer.
    376      * @throws IOException
    377      */
    378     protected abstract int prepareUncheckedAddContent() throws IOException;
    379
    380     /* ------------------------------------------------------------ */
    381     void uncheckedAddContent(int b)
    382     {
    383         _buffer.put((byte)b);
    384     }
    385
    386     /* ------------------------------------------------------------ */
    387     void completeUncheckedAddContent()
    388     {
    389         if (_noContent)
    390         {
    391             if(_buffer!=null)
    392                 _buffer.clear();
    393             return;
    394         }
    395         else
    396         {
    397             _contentWritten+=_buffer.length();
    398             if (_head)
    399                 _buffer.clear();
    400         }
    401     }
    402
    403     /* ------------------------------------------------------------ */
    404     public boolean isBufferFull()
    405     {
    406         if (_buffer != null && _buffer.space()==0)
    407         {
    408             if (_buffer.length()==0 && !_buffer.isImmutable())
    409                 _buffer.compact();
    410             return _buffer.space()==0;
    411         }
    412
    413         return _content!=null && _content.length()>0;
    414     }
    415
    416     /* ------------------------------------------------------------ */
    417     public boolean isContentWritten()
    418     {
    419         return _contentLength>=0 && _contentWritten>=_contentLength;
    420     }
    421
    422     /* ------------------------------------------------------------ */
    423     public abstract void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException;
    424
    425     /* ------------------------------------------------------------ */
    426     /**
    427      * Complete the message.
    428      *
    429      * @throws IOException
    430      */
    431     public void complete() throws IOException
    432     {
    433         if (_state == STATE_HEADER)
    434         {
    435             throw new IllegalStateException("State==HEADER");
    436         }
    437
    438         if (_contentLength >= 0 && _contentLength != _contentWritten && !_head)
    439         {
    440             if (Log.isDebugEnabled())
    441                 Log.debug("ContentLength written=="+_contentWritten+" != contentLength=="+_contentLength);
    442             _close = true;
    443         }
    444     }
    445
    446     /* ------------------------------------------------------------ */
    447     public abstract long flush() throws IOException;
    448
    449
    450     /* ------------------------------------------------------------ */
    451     /**
    452      * Utility method to send an error response. If the builder is not committed, this call is
    453      * equivalent to a setResponse, addcontent and complete call.
    454      *
    455      * @param code
    456      * @param reason
    457      * @param content
    458      * @param close
    459      * @throws IOException
    460      */
    461     public void sendError(int code, String reason, String content, boolean close) throws IOException
    462     {
    463         if (close)
    464             _close = close;
    465         if (!isCommitted())
    466         {
    467             setResponse(code, reason);
    468             completeHeader(null, false);
    469             if (content != null)
    470                 addContent(new View(new ByteArrayBuffer(content)), Generator.LAST);
    471             complete();
    472         }
    473     }
    474
    475     /* ------------------------------------------------------------ */
    476     /**
    477      * @return Returns the contentWritten.
    478      */
    479     public long getContentWritten()
    480     {
    481         return _contentWritten;
    482     }
    483
    484
    485     /* ------------------------------------------------------------ */
    486     /* ------------------------------------------------------------ */
    487     /* ------------------------------------------------------------ */
    488     /* ------------------------------------------------------------ */
    489     /** Output.
    490      *
    491      * <p>
    492      * Implements  {@link javax.servlet.ServletOutputStream} from the {@link javax.servlet} package.
    493      * </p>
    494      * A {@link ServletOutputStream} implementation that writes content
    495      * to a {@link AbstractGenerator}.   The class is designed to be reused
    496      * and can be reopened after a close.
    497      */
    498     public static class Output extends ServletOutputStream
    499     {
    500         protected AbstractGenerator _generator;
    501         protected long _maxIdleTime;
    502         protected ByteArrayBuffer _buf = new ByteArrayBuffer(NO_BYTES);
    503         protected boolean _closed;
    504
    505         // These are held here for reuse by Writer
    506         String _characterEncoding;
    507         Writer _converter;
    508         char[] _chars;
    509         ByteArrayOutputStream2 _bytes;
    510
    511
    512         /* ------------------------------------------------------------ */
    513         public Output(AbstractGenerator generator, long maxIdleTime)
    514         {
    515             _generator=generator;
    516             _maxIdleTime=maxIdleTime;
    517         }
    518
    519         /* ------------------------------------------------------------ */
    520         /*
    521          * @see java.io.OutputStream#close()
    522          */
    523         public void close() throws IOException
    524         {
    525             _closed=true;
    526         }
    527
    528         /* ------------------------------------------------------------ */
    529         void  blockForOutput() throws IOException
    530         {
    531             if (_generator._endp.isBlocking())
    532             {
    533                 try
    534                 {
    535                     flush();
    536                 }
    537                 catch(IOException e)
    538                 {
    539                     _generator._endp.close();
    540                     throw e;
    541                 }
    542             }
    543             else
    544             {
    545                 if (!_generator._endp.blockWritable(_maxIdleTime))
    546                 {
    547                     _generator._endp.close();
    548                     throw new EofException("timeout");
    549                 }
    550
    551                 _generator.flush();
    552             }
    553         }
    554
    555         /* ------------------------------------------------------------ */
    556         void reopen()
    557         {
    558             _closed=false;
    559         }
    560
    561         /* ------------------------------------------------------------ */
    562         public void flush() throws IOException
    563         {
    564             // block until everything is flushed
    565             Buffer content = _generator._content;
    566             Buffer buffer = _generator._buffer;
    567             if (content!=null && content.length()>0 || buffer!=null && buffer.length()>0 || _generator.isBufferFull())
    568             {
    569                 _generator.flush();
    570
    571                 while ((content!=null && content.length()>0 ||buffer!=null && buffer.length()>0) && _generator._endp.isOpen())
    572                     blockForOutput();
    573             }
    574         }
    575
    576         /* ------------------------------------------------------------ */
    577         public void write(byte[] b, int off, int len) throws IOException
    578         {
    579             _buf.wrap(b, off, len);
    580             write(_buf);
    581             _buf.wrap(NO_BYTES);
    582         }
    583
    584         /* ------------------------------------------------------------ */
    585         /*
    586          * @see java.io.OutputStream#write(byte[])
    587          */
    588         public void write(byte[] b) throws IOException
    589         {
    590             _buf.wrap(b);
    591             write(_buf);
    592             _buf.wrap(NO_BYTES);
    593         }
    594
    595         /* ------------------------------------------------------------ */
    596         /*
    597          * @see java.io.OutputStream#write(int)
    598          */
    599         public void write(int b) throws IOException
    600         {
    601             if (_closed)
    602                 throw new IOException("Closed");
    603             if (!_generator._endp.isOpen())
    604                 throw new EofException();
    605
    606             // Block until we can add _content.
    607             while (_generator.isBufferFull())
    608             {
    609                 blockForOutput();
    610                 if (_closed)
    611                     throw new IOException("Closed");
    612                 if (!_generator._endp.isOpen())
    613                     throw new EofException();
    614             }
    615
    616             // Add the _content
    617             if (_generator.addContent((byte)b))
    618                 // Buffers are full so flush.
    619                 flush();
    620
    621             if (_generator.isContentWritten())
    622             {
    623                 flush();
    624                 close();
    625             }
    626         }
    627
    628         /* ------------------------------------------------------------ */
    629         private void write(Buffer buffer) throws IOException
    630         {
    631             if (_closed)
    632                 throw new IOException("Closed");
    633             if (!_generator._endp.isOpen())
    634                 throw new EofException();
    635
    636             // Block until we can add _content.
    637             while (_generator.isBufferFull())
    638             {
    639                 blockForOutput();
    640                 if (_closed)
    641                     throw new IOException("Closed");
    642                 if (!_generator._endp.isOpen())
    643                     throw new EofException();
    644             }
    645
    646             // Add the _content
    647             _generator.addContent(buffer, Generator.MORE);
    648
    649             // Have to flush and complete headers?
    650             if (_generator.isBufferFull())
    651                 flush();
    652
    653             if (_generator.isContentWritten())
    654             {
    655                 flush();
    656                 close();
    657             }
    658
    659             // Block until our buffer is free
    660             while (buffer.length() > 0 && _generator._endp.isOpen())
    661                 blockForOutput();
    662         }
    663
    664         /* ------------------------------------------------------------ */
    665         /*
    666          * @see javax.servlet.ServletOutputStream#print(java.lang.String)
    667          */
    668         public void print(String s) throws IOException
    669         {
    670             write(s.getBytes());
    671         }
    672     }
    673
    674     /* ------------------------------------------------------------ */
    675     /* ------------------------------------------------------------ */
    676     /* ------------------------------------------------------------ */
    677     /** OutputWriter.
    678      * A writer that can wrap a {@link Output} stream and provide
    679      * character encodings.
    680      *
    681      * The UTF-8 encoding is done by this class and no additional
    682      * buffers or Writers are used.
    683      * The UTF-8 code was inspired by http://javolution.org
    684      */
    685     public static class OutputWriter extends Writer
    686     {
    687         private static final int WRITE_CONV = 0;
    688         private static final int WRITE_ISO1 = 1;
    689         private static final int WRITE_UTF8 = 2;
    690
    691         Output _out;
    692         AbstractGenerator _generator;
    693         int _writeMode;
    694         int _surrogate;
    695
    696         /* ------------------------------------------------------------ */
    697         public OutputWriter(Output out)
    698         {
    699             _out=out;
    700             _generator=_out._generator;
    701
    702         }
    703
    704         /* ------------------------------------------------------------ */
    705         public void setCharacterEncoding(String encoding)
    706         {
    707             if (encoding == null || StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding))
    708             {
    709                 _writeMode = WRITE_ISO1;
    710             }
    711             else if (StringUtil.__UTF8.equalsIgnoreCase(encoding))
    712             {
    713                 _writeMode = WRITE_UTF8;
    714             }
    715             else
    716             {
    717                 _writeMode = WRITE_CONV;
    718                 if (_out._characterEncoding == null || !_out._characterEncoding.equalsIgnoreCase(encoding))
    719                     _out._converter = null; // Set lazily in getConverter()
    720             }
    721
    722             _out._characterEncoding = encoding;
    723             if (_out._bytes==null)
    724                 _out._bytes = new ByteArrayOutputStream2(MAX_OUTPUT_CHARS);
    725         }
    726
    727         /* ------------------------------------------------------------ */
    728         public void close() throws IOException
    729         {
    730             _out.close();
    731         }
    732
    733         /* ------------------------------------------------------------ */
    734         public void flush() throws IOException
    735         {
    736             _out.flush();
    737         }
    738
    739         /* ------------------------------------------------------------ */
    740         public void write (String s,int offset, int length) throws IOException
    741         {
    742             while (length > MAX_OUTPUT_CHARS)
    743             {
    744                 write(s, offset, MAX_OUTPUT_CHARS);
    745                 offset += MAX_OUTPUT_CHARS;
    746                 length -= MAX_OUTPUT_CHARS;
    747             }
    748
    749             if (_out._chars==null)
    750             {
    751                 _out._chars = new char[MAX_OUTPUT_CHARS];
    752             }
    753             char[] chars = _out._chars;
    754             s.getChars(offset, offset + length, chars, 0);
    755             write(chars, 0, length);
    756         }
    757
    758         /* ------------------------------------------------------------ */
    759         public void write (char[] s,int offset, int length) throws IOException
    760         {
    761             Output out = _out;
    762
    763             while (length > 0)
    764             {
    765                 out._bytes.reset();
    766                 int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
    767
    768                 switch (_writeMode)
    769                 {
    770                     case WRITE_CONV:
    771                     {
    772                         Writer converter=getConverter();
    773                         converter.write(s, offset, chars);
    774                         converter.flush();
    775                     }
    776                     break;
    777
    778                     case WRITE_ISO1:
    779                     {
    780                         byte[] buffer=out._bytes.getBuf();
    781                         int bytes=out._bytes.getCount();
    782
    783                         if (chars>buffer.length-bytes)
    784                             chars=buffer.length-bytes;
    785
    786                         for (int i = 0; i < chars; i++)
    787                         {
    788                             int c = s[offset+i];
    789                             buffer[bytes++]=(byte)(c<256?c:'?'); // ISO-1 and UTF-8 match for 0 - 255
    790                         }
    791                         if (bytes>=0)
    792                             out._bytes.setCount(bytes);
    793
    794                         break;
    795                     }
    796
    797                     case WRITE_UTF8:
    798                     {
    799                         byte[] buffer=out._bytes.getBuf();
    800                         int bytes=out._bytes.getCount();
    801
    802                         if (bytes+chars>buffer.length)
    803                             chars=buffer.length-bytes;
    804
    805                         for (int i = 0; i < chars; i++)
    806                         {
    807                             int code = s[offset+i];
    808
    809                             if ((code & 0xffffff80) == 0)
    810                             {
    811                                 // 1b
    812                                 if (bytes+1>buffer.length)
    813                                 {
    814                                     chars=i;
    815                                     break;
    816                                 }
    817                                 buffer[bytes++]=(byte)(code);
    818                             }
    819                             else
    820 			    {
    821 				if((code&0xfffff800)==0)
    822 				{
    823 				    // 2b
    824 				    if (bytes+2>buffer.length)
    825 				    {
    826 					chars=i;
    827 					break;
    828 				    }
    829 				    buffer[bytes++]=(byte)(0xc0|(code>>6));
    830 				    buffer[bytes++]=(byte)(0x80|(code&0x3f));
    831 				}
    832 				else if((code&0xffff0000)==0)
    833 				{
    834 				    // 3b
    835 				    if (bytes+3>buffer.length)
    836 				    {
    837 					chars=i;
    838 					break;
    839 				    }
    840 				    buffer[bytes++]=(byte)(0xe0|(code>>12));
    841 				    buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
    842 				    buffer[bytes++]=(byte)(0x80|(code&0x3f));
    843 				}
    844 				else if((code&0xff200000)==0)
    845 				{
    846 				    // 4b
    847 				    if (bytes+4>buffer.length)
    848 				    {
    849 					chars=i;
    850 					break;
    851 				    }
    852 				    buffer[bytes++]=(byte)(0xf0|(code>>18));
    853 				    buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
    854 				    buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
    855 				    buffer[bytes++]=(byte)(0x80|(code&0x3f));
    856 				}
    857 				else if((code&0xf4000000)==0)
    858 				{
    859 				    // 5b
    860 				    if (bytes+5>buffer.length)
    861 				    {
    862 					chars=i;
    863 					break;
    864 				    }
    865 				    buffer[bytes++]=(byte)(0xf8|(code>>24));
    866 				    buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
    867 				    buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
    868 				    buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
    869 				    buffer[bytes++]=(byte)(0x80|(code&0x3f));
    870 				}
    871 				else if((code&0x80000000)==0)
    872 				{
    873 				    // 6b
    874 				    if (bytes+6>buffer.length)
    875 				    {
    876 					chars=i;
    877 					break;
    878 				    }
    879 				    buffer[bytes++]=(byte)(0xfc|(code>>30));
    880 				    buffer[bytes++]=(byte)(0x80|((code>>24)&0x3f));
    881 				    buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
    882 				    buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
    883 				    buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
    884 				    buffer[bytes++]=(byte)(0x80|(code&0x3f));
    885 				}
    886 				else
    887 				{
    888 				    buffer[bytes++]=(byte)('?');
    889 				}
    890 				if (bytes==buffer.length)
    891 				{
    892 				    chars=i+1;
    893 				    break;
    894 				}
    895 			    }
    896                         }
    897                         out._bytes.setCount(bytes);
    898                         break;
    899                     }
    900                     default:
    901                         throw new IllegalStateException();
    902                 }
    903
    904                 out._bytes.writeTo(out);
    905                 length-=chars;
    906                 offset+=chars;
    907             }
    908         }
    909
    910         /* ------------------------------------------------------------ */
    911         private Writer getConverter() throws IOException
    912         {
    913             if (_out._converter == null)
    914                 _out._converter = new OutputStreamWriter(_out._bytes, _out._characterEncoding);
    915             return _out._converter;
    916         }
    917     }
    918 }