View Javadoc

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