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.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)
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 }