001 /*
002 * Copyright (c) 2001-2004 Caucho Technology, Inc. All rights reserved.
003 *
004 * The Apache Software License, Version 1.1
005 *
006 * Redistribution and use in source and binary forms, with or without
007 * modification, are permitted provided that the following conditions
008 * are met:
009 *
010 * 1. Redistributions of source code must retain the above copyright
011 * notice, this list of conditions and the following disclaimer.
012 *
013 * 2. Redistributions in binary form must reproduce the above copyright
014 * notice, this list of conditions and the following disclaimer in
015 * the documentation and/or other materials provided with the
016 * distribution.
017 *
018 * 3. The end-user documentation included with the redistribution, if
019 * any, must include the following acknowlegement:
020 * "This product includes software developed by the
021 * Caucho Technology (http://www.caucho.com/)."
022 * Alternately, this acknowlegement may appear in the software itself,
023 * if and wherever such third-party acknowlegements normally appear.
024 *
025 * 4. The names "Burlap", "Resin", and "Caucho" must not be used to
026 * endorse or promote products derived from this software without prior
027 * written permission. For written permission, please contact
028 * info@caucho.com.
029 *
030 * 5. Products derived from this software may not be called "Resin"
031 * nor may "Resin" appear in their names without prior written
032 * permission of Caucho Technology.
033 *
034 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
035 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
036 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
037 * DISCLAIMED. IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS
038 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
039 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
040 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
041 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
042 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
043 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
044 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
045 *
046 * @author Scott Ferguson
047 */
048
049 package com.caucho.burlap.client;
050
051 import java.io.ByteArrayOutputStream;
052 import java.io.IOException;
053 import java.io.InputStream;
054 import java.util.Calendar;
055 import java.util.Date;
056 import java.util.Hashtable;
057 import java.util.TimeZone;
058 import java.util.Vector;
059
060 /**
061 * Input stream for Burlap requests, compatible with microedition
062 * Java. It only uses classes and types available to J2ME. In
063 * particular, it does not have any support for the <double> type.
064 *
065 * <p>MicroBurlapInput does not depend on any classes other than
066 * in J2ME, so it can be extracted independently into a smaller package.
067 *
068 * <p>MicroBurlapInput is unbuffered, so any client needs to provide
069 * its own buffering.
070 *
071 * <pre>
072 * InputStream is = ...; // from http connection
073 * MicroBurlapInput in = new MicroBurlapInput(is);
074 * String value;
075 *
076 * in.startReply(); // read reply header
077 * value = in.readString(); // read string value
078 * in.completeReply(); // read reply footer
079 * </pre>
080 */
081 public class MicroBurlapInput {
082 private static int base64Decode[];
083
084 private InputStream is;
085 protected int peek;
086 protected boolean peekTag;
087 protected Date date;
088 protected Calendar utcCalendar;
089 private Calendar localCalendar;
090 protected Vector refs;
091 protected String method;
092 protected StringBuffer sbuf = new StringBuffer();
093 protected StringBuffer entity = new StringBuffer();
094
095 /**
096 * Creates a new Burlap input stream, initialized with an
097 * underlying input stream.
098 *
099 * @param is the underlying input stream.
100 */
101 public MicroBurlapInput(InputStream is)
102 {
103 init(is);
104 }
105
106 /**
107 * Creates an uninitialized Burlap input stream.
108 */
109 public MicroBurlapInput()
110 {
111 }
112
113 /**
114 * Returns a call's method.
115 */
116 public String getMethod()
117 {
118 return method;
119 }
120
121 /**
122 * Initialize the Burlap input stream with a new underlying stream.
123 * Applications can use <code>init(InputStream)</code> to reuse
124 * MicroBurlapInput to save garbage collection.
125 */
126 public void init(InputStream is)
127 {
128 this.is = is;
129 this.refs = null;
130 }
131
132 /**
133 * Starts reading the call
134 *
135 * <p>A successful completion will have a single value:
136 *
137 * <pre>
138 * <burlap:call>
139 * <method>method</method>
140 * </pre>
141 */
142 public void startCall()
143 throws IOException
144 {
145 expectStartTag("burlap:call");
146 expectStartTag("method");
147 method = parseString();
148 expectEndTag("method");
149 this.refs = null;
150 }
151
152 /**
153 * Completes reading the call.
154 *
155 * <pre>
156 * </burlap:call>
157 * </pre>
158 */
159 public void completeCall()
160 throws IOException
161 {
162 expectEndTag("burlap:call");
163 }
164
165 /**
166 * Reads a reply as an object.
167 * If the reply has a fault, throws the exception.
168 */
169 public Object readReply(Class expectedClass)
170 throws Exception
171 {
172 if (startReply()) {
173 Object value = readObject(expectedClass);
174 completeReply();
175 return value;
176 }
177 else {
178 Hashtable fault = readFault();
179
180 Object detail = fault.get("detail");
181 if (detail instanceof Exception)
182 throw (Exception) detail;
183
184 else {
185 String code = (String) fault.get("code");
186 String message = (String) fault.get("message");
187
188 throw new BurlapServiceException(message, code, detail);
189 }
190 }
191 }
192
193 /**
194 * Starts reading the reply.
195 *
196 * <p>A successful completion will have a single value. An unsuccessful
197 * one will have a fault:
198 *
199 * <pre>
200 * <burlap:reply>
201 * </pre>
202 *
203 * @return true if success, false for fault.
204 */
205 public boolean startReply()
206 throws IOException
207 {
208 this.refs = null;
209
210 expectStartTag("burlap:reply");
211
212 if (! parseTag())
213 throw new BurlapProtocolException("expected <value>");
214
215 String tag = sbuf.toString();
216 if (tag.equals("fault")) {
217 peekTag = true;
218 return false;
219 }
220 else {
221 peekTag = true;
222 return true;
223 }
224 }
225
226 /**
227 * Completes reading the reply.
228 *
229 * <pre>
230 * </burlap:reply>
231 * </pre>
232 */
233 public void completeReply()
234 throws IOException
235 {
236 expectEndTag("burlap:reply");
237 }
238
239 /**
240 * Reads a boolean value from the input stream.
241 */
242 public boolean readBoolean()
243 throws IOException
244 {
245 expectStartTag("boolean");
246
247 int value = parseInt();
248
249 expectEndTag("boolean");
250
251 return value != 0;
252 }
253
254 /**
255 * Reads an integer value from the input stream.
256 */
257 public int readInt()
258 throws IOException
259 {
260 expectStartTag("int");
261
262 int value = parseInt();
263
264 expectEndTag("int");
265
266 return value;
267 }
268
269 /**
270 * Reads a long value from the input stream.
271 */
272 public long readLong()
273 throws IOException
274 {
275 expectStartTag("long");
276
277 long value = parseLong();
278
279 expectEndTag("long");
280
281 return value;
282 }
283
284 /**
285 * Reads a date value from the input stream.
286 */
287 public long readUTCDate()
288 throws IOException
289 {
290 expectStartTag("date");
291
292 if (utcCalendar == null)
293 utcCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
294
295 long value = parseDate(utcCalendar);
296
297 expectEndTag("date");
298
299 return value;
300 }
301
302 /**
303 * Reads a date value from the input stream.
304 */
305 public long readLocalDate()
306 throws IOException
307 {
308 expectStartTag("date");
309
310 if (localCalendar == null)
311 localCalendar = Calendar.getInstance();
312
313 long value = parseDate(localCalendar);
314
315 expectEndTag("date");
316
317 return value;
318 }
319
320 /**
321 * Reads a remote value from the input stream.
322 */
323 public BurlapRemote readRemote()
324 throws IOException
325 {
326 expectStartTag("remote");
327
328 String type = readType();
329 String url = readString();
330
331 expectEndTag("remote");
332
333 return new BurlapRemote(type, url);
334 }
335
336 /**
337 * Reads a string value from the input stream.
338 *
339 * <p>The two valid possibilities are either a <null>
340 * or a <string>. The string value is encoded in utf-8, and
341 * understands the basic XML escapes: "&123;", "<", ">",
342 * "'", """.
343 *
344 * <pre>
345 * <null></null>
346 * <string>a utf-8 encoded string</string>
347 * </pre>
348 */
349 public String readString()
350 throws IOException
351 {
352 if (! parseTag())
353 throw new BurlapProtocolException("expected <string>");
354
355 String tag = sbuf.toString();
356 if (tag.equals("null")) {
357 expectEndTag("null");
358 return null;
359 }
360 else if (tag.equals("string")) {
361 sbuf.setLength(0);
362 parseString(sbuf);
363 String value = sbuf.toString();
364 expectEndTag("string");
365 return value;
366 }
367 else
368 throw expectBeginTag("string", tag);
369 }
370
371 /**
372 * Reads a byte array from the input stream.
373 *
374 * <p>The two valid possibilities are either a <null>
375 * or a <base64>.
376 */
377 public byte []readBytes()
378 throws IOException
379 {
380 if (! parseTag())
381 throw new BurlapProtocolException("expected <base64>");
382
383 String tag = sbuf.toString();
384 if (tag.equals("null")) {
385 expectEndTag("null");
386 return null;
387 }
388 else if (tag.equals("base64")) {
389 sbuf.setLength(0);
390 byte []value = parseBytes();
391 expectEndTag("base64");
392 return value;
393 }
394 else
395 throw expectBeginTag("base64", tag);
396 }
397
398 /**
399 * Reads an arbitrary object the input stream.
400 */
401 public Object readObject(Class expectedClass)
402 throws IOException
403 {
404 if (! parseTag())
405 throw new BurlapProtocolException("expected <tag>");
406
407 String tag = sbuf.toString();
408 if (tag.equals("null")) {
409 expectEndTag("null");
410 return null;
411 }
412 else if (tag.equals("boolean")) {
413 int value = parseInt();
414 expectEndTag("boolean");
415 return new Boolean(value != 0);
416 }
417 else if (tag.equals("int")) {
418 int value = parseInt();
419 expectEndTag("int");
420 return new Integer(value);
421 }
422 else if (tag.equals("long")) {
423 long value = parseLong();
424 expectEndTag("long");
425 return new Long(value);
426 }
427 else if (tag.equals("string")) {
428 sbuf.setLength(0);
429 parseString(sbuf);
430 String value = sbuf.toString();
431 expectEndTag("string");
432 return value;
433 }
434 else if (tag.equals("xml")) {
435 sbuf.setLength(0);
436 parseString(sbuf);
437 String value = sbuf.toString();
438 expectEndTag("xml");
439 return value;
440 }
441 else if (tag.equals("date")) {
442 if (utcCalendar == null)
443 utcCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
444
445 long value = parseDate(utcCalendar);
446 expectEndTag("date");
447 return new Date(value);
448 }
449 else if (tag.equals("map")) {
450 String type = readType();
451
452 return readMap(expectedClass, type);
453 }
454 else if (tag.equals("list")) {
455 String type = readType();
456
457 int length = readLength();
458
459 return readList(expectedClass, type, length);
460 }
461 else if (tag.equals("ref")) {
462 int value = parseInt();
463 expectEndTag("ref");
464
465 return refs.elementAt(value);
466 }
467 else if (tag.equals("remote")) {
468 String type = readType();
469 String url = readString();
470
471 expectEndTag("remote");
472
473 return resolveRemote(type, url);
474 }
475 else
476 return readExtensionObject(expectedClass, tag);
477 }
478
479 /**
480 * Reads a type value from the input stream.
481 *
482 * <pre>
483 * <type>a utf-8 encoded string</type>
484 * </pre>
485 */
486 public String readType()
487 throws IOException
488 {
489 if (! parseTag())
490 throw new BurlapProtocolException("expected <type>");
491
492 String tag = sbuf.toString();
493 if (! tag.equals("type"))
494 throw new BurlapProtocolException("expected <type>");
495
496 sbuf.setLength(0);
497 parseString(sbuf);
498 String value = sbuf.toString();
499 expectEndTag("type");
500
501 return value;
502 }
503
504 /**
505 * Reads a length value from the input stream. If the length isn't
506 * specified, returns -1.
507 *
508 * <pre>
509 * <length>integer</length>
510 * </pre>
511 */
512 public int readLength()
513 throws IOException
514 {
515 expectStartTag("length");
516
517 int ch = skipWhitespace();
518
519 peek = ch;
520
521 if (ch == '<') {
522 expectEndTag("length");
523 return -1;
524 }
525
526 int value = parseInt();
527
528 expectEndTag("length");
529
530 return value;
531 }
532
533 /**
534 * Resolves a remote object.
535 */
536 public Object resolveRemote(String type, String url)
537 throws IOException
538 {
539 return new BurlapRemote(type, url);
540 }
541
542 /**
543 * Reads a fault.
544 */
545 public Hashtable readFault()
546 throws IOException
547 {
548 expectStartTag("fault");
549
550 Hashtable map = new Hashtable();
551
552 while (parseTag()) {
553 peekTag = true;
554 Object key = readObject(null);
555 Object value = readObject(null);
556
557 if (key != null && value != null)
558 map.put(key, value);
559 }
560
561 if (! sbuf.toString().equals("fault"))
562 throw new BurlapProtocolException("expected </fault>");
563
564 return map;
565 }
566
567 /**
568 * Reads an object from the input stream.
569 *
570 * @param expectedClass the calling routine's expected class
571 * @param type the type from the stream
572 */
573 public Object readMap(Class expectedClass, String type)
574 throws IOException
575 {
576 Hashtable map = new Hashtable();
577 if (refs == null)
578 refs = new Vector();
579 refs.addElement(map);
580
581 while (parseTag()) {
582 peekTag = true;
583 Object key = readObject(null);
584 Object value = readObject(null);
585
586 map.put(key, value);
587 }
588 if (! sbuf.toString().equals("map"))
589 throw new BurlapProtocolException("expected </map>");
590
591 return map;
592 }
593
594 /**
595 * Reads object unknown to MicroBurlapInput.
596 */
597 protected Object readExtensionObject(Class expectedClass, String tag)
598 throws IOException
599 {
600 throw new BurlapProtocolException("unknown object tag <" + tag + ">");
601 }
602
603 /**
604 * Reads a list object from the input stream.
605 *
606 * @param expectedClass the calling routine's expected class
607 * @param type the type from the stream
608 * @param length the expected length, -1 for unspecified length
609 */
610 public Object readList(Class expectedClass, String type, int length)
611 throws IOException
612 {
613 Vector list = new Vector();
614 if (refs == null)
615 refs = new Vector();
616 refs.addElement(list);
617
618 while (parseTag()) {
619 peekTag = true;
620 Object value = readObject(null);
621
622 list.addElement(value);
623 }
624
625 if (! sbuf.toString().equals("list"))
626 throw new BurlapProtocolException("expected </list>");
627
628 return list;
629 }
630
631 /**
632 * Parses an integer value from the stream.
633 */
634 protected int parseInt()
635 throws IOException
636 {
637 int sign = 1;
638 int value = 0;
639
640 int ch = skipWhitespace();
641 if (ch == '+')
642 ch = read();
643 else if (ch == '-') {
644 sign = -1;
645 ch = read();
646 }
647
648 for (; ch >= '0' && ch <= '9'; ch = read())
649 value = 10 * value + ch - '0';
650
651 peek = ch;
652
653 return sign * value;
654 }
655
656 /**
657 * Parses a long value from the stream.
658 */
659 protected long parseLong()
660 throws IOException
661 {
662 long sign = 1;
663 long value = 0;
664
665 int ch = skipWhitespace();
666 if (ch == '+')
667 ch = read();
668 else if (ch == '-') {
669 sign = -1;
670 ch = read();
671 }
672
673 for (; ch >= '0' && ch <= '9'; ch = read()) {
674 value = 10 * value + ch - '0';
675 }
676
677 peek = ch;
678
679 return sign * value;
680 }
681
682 /**
683 * Parses a date value from the stream.
684 */
685 protected long parseDate(Calendar calendar)
686 throws IOException
687 {
688 int ch = skipWhitespace();
689
690 int year = 0;
691 for (int i = 0; i < 4; i++) {
692 if (ch >= '0' && ch <= '9')
693 year = 10 * year + ch - '0';
694 else
695 throw expectedChar("year", ch);
696
697 ch = read();
698 }
699
700 int month = 0;
701 for (int i = 0; i < 2; i++) {
702 if (ch >= '0' && ch <= '9')
703 month = 10 * month + ch - '0';
704 else
705 throw expectedChar("month", ch);
706
707 ch = read();
708 }
709
710 int day = 0;
711 for (int i = 0; i < 2; i++) {
712 if (ch >= '0' && ch <= '9')
713 day = 10 * day + ch - '0';
714 else
715 throw expectedChar("day", ch);
716
717 ch = read();
718 }
719
720 if (ch != 'T')
721 throw expectedChar("`T'", ch);
722
723 ch = read();
724
725 int hour = 0;
726 for (int i = 0; i < 2; i++) {
727 if (ch >= '0' && ch <= '9')
728 hour = 10 * hour + ch - '0';
729 else
730 throw expectedChar("hour", ch);
731
732 ch = read();
733 }
734
735 int minute = 0;
736 for (int i = 0; i < 2; i++) {
737 if (ch >= '0' && ch <= '9')
738 minute = 10 * minute + ch - '0';
739 else
740 throw expectedChar("minute", ch);
741
742 ch = read();
743 }
744
745 int second = 0;
746 for (int i = 0; i < 2; i++) {
747 if (ch >= '0' && ch <= '9')
748 second = 10 * second + ch - '0';
749 else
750 throw expectedChar("second", ch);
751
752 ch = read();
753 }
754
755 for (; ch > 0 && ch != '<'; ch = read()) {
756 }
757
758 peek = ch;
759
760 calendar.set(Calendar.YEAR, year);
761 calendar.set(Calendar.MONTH, month - 1);
762 calendar.set(Calendar.DAY_OF_MONTH, day);
763 calendar.set(Calendar.HOUR_OF_DAY, hour);
764 calendar.set(Calendar.MINUTE, minute);
765 calendar.set(Calendar.SECOND, second);
766 calendar.set(Calendar.MILLISECOND, 0);
767
768 return calendar.getTime().getTime();
769 }
770
771 /**
772 * Parses a string value from the stream.
773 * string buffer is used for the result.
774 */
775 protected String parseString()
776 throws IOException
777 {
778 StringBuffer sbuf = new StringBuffer();
779
780 return parseString(sbuf).toString();
781 }
782
783 /**
784 * Parses a string value from the stream. The burlap object's
785 * string buffer is used for the result.
786 */
787 protected StringBuffer parseString(StringBuffer sbuf)
788 throws IOException
789 {
790 int ch = read();
791
792 for (; ch >= 0 && ch != '<'; ch = read()) {
793 if (ch == '&') {
794 ch = read();
795
796 if (ch == '#') {
797 ch = read();
798
799 if (ch >= '0' && ch <= '9') {
800 int v = 0;
801 for (; ch >= '0' && ch <= '9'; ch = read()) {
802 v = 10 * v + ch - '0';
803 }
804
805 sbuf.append((char) v);
806 }
807 }
808 else {
809 StringBuffer entityBuffer = new StringBuffer();
810
811 for (; ch >= 'a' && ch <= 'z'; ch = read())
812 entityBuffer.append((char) ch);
813
814 String entity = entityBuffer.toString();
815 if (entity.equals("amp"))
816 sbuf.append('&');
817 else if (entity.equals("apos"))
818 sbuf.append('\'');
819 else if (entity.equals("quot"))
820 sbuf.append('"');
821 else if (entity.equals("lt"))
822 sbuf.append('<');
823 else if (entity.equals("gt"))
824 sbuf.append('>');
825 else
826 throw new BurlapProtocolException("unknown XML entity &" + entity + "; at `" + (char) ch + "'");
827 }
828
829 if (ch != ';')
830 throw expectedChar("';'", ch);
831 }
832 else if (ch < 0x80)
833 sbuf.append((char) ch);
834 else if ((ch & 0xe0) == 0xc0) {
835 int ch1 = read();
836 int v = ((ch & 0x1f) << 6) + (ch1 & 0x3f);
837
838 sbuf.append((char) v);
839 }
840 else if ((ch & 0xf0) == 0xe0) {
841 int ch1 = read();
842 int ch2 = read();
843 int v = ((ch & 0x0f) << 12) + ((ch1 & 0x3f) << 6) + (ch2 & 0x3f);
844
845 sbuf.append((char) v);
846 }
847 else
848 throw new BurlapProtocolException("bad utf-8 encoding");
849 }
850
851 peek = ch;
852
853 return sbuf;
854 }
855
856 /**
857 * Parses a byte array.
858 */
859 protected byte []parseBytes()
860 throws IOException
861 {
862 ByteArrayOutputStream bos = new ByteArrayOutputStream();
863
864 parseBytes(bos);
865
866 return bos.toByteArray();
867 }
868
869 /**
870 * Parses a byte array.
871 */
872 protected ByteArrayOutputStream parseBytes(ByteArrayOutputStream bos)
873 throws IOException
874 {
875 int ch;
876 for (ch = read(); ch >= 0 && ch != '<'; ch = read()) {
877 int b1 = ch;
878 int b2 = read();
879 int b3 = read();
880 int b4 = read();
881
882 if (b4 != '=') {
883 int chunk = ((base64Decode[b1] << 18) +
884 (base64Decode[b2] << 12) +
885 (base64Decode[b3] << 6) +
886 (base64Decode[b4]));
887
888 bos.write(chunk >> 16);
889 bos.write(chunk >> 8);
890 bos.write(chunk);
891 }
892 else if (b3 != '=') {
893 int chunk = ((base64Decode[b1] << 12) +
894 (base64Decode[b2] << 6) +
895 (base64Decode[b3]));
896
897 bos.write(chunk >> 8);
898 bos.write(chunk);
899 }
900 else {
901 int chunk = ((base64Decode[b1] << 6) +
902 (base64Decode[b2]));
903
904 bos.write(chunk);
905 }
906 }
907
908 if (ch == '<')
909 peek = ch;
910
911 return bos;
912 }
913
914 protected void expectStartTag(String tag)
915 throws IOException
916 {
917 if (! parseTag())
918 throw new BurlapProtocolException("expected <" + tag + ">");
919
920 if (! sbuf.toString().equals(tag))
921 throw new BurlapProtocolException("expected <" + tag + "> at <" + sbuf + ">");
922 }
923
924 protected void expectEndTag(String tag)
925 throws IOException
926 {
927 if (parseTag())
928 throw new BurlapProtocolException("expected </" + tag + ">");
929
930 if (! sbuf.toString().equals(tag))
931 throw new BurlapProtocolException("expected </" + tag + "> at </" + sbuf + ">");
932 }
933
934 /**
935 * Parses a tag. Returns true if it's a start tag.
936 */
937 protected boolean parseTag()
938 throws IOException
939 {
940 if (peekTag) {
941 peekTag = false;
942 return true;
943 }
944
945 int ch = skipWhitespace();
946 boolean isStartTag = true;
947
948 if (ch != '<')
949 throw expectedChar("'<'", ch);
950
951 ch = read();
952 if (ch == '/') {
953 isStartTag = false;
954 ch = is.read();
955 }
956
957 if (! isTagChar(ch))
958 throw expectedChar("tag", ch);
959
960 sbuf.setLength(0);
961 for (; isTagChar(ch); ch = read())
962 sbuf.append((char) ch);
963
964 if (ch != '>')
965 throw expectedChar("'>'", ch);
966
967 return isStartTag;
968 }
969
970 protected IOException expectedChar(String expect, int actualChar)
971 {
972 return new BurlapProtocolException("expected " + expect + " at " +
973 (char) actualChar + "'");
974 }
975
976 protected IOException expectBeginTag(String expect, String tag)
977 {
978 return new BurlapProtocolException("expected <" + expect + "> at <" + tag + ">");
979 }
980
981 private boolean isTagChar(int ch)
982 {
983 return (ch >= 'a' && ch <= 'z' ||
984 ch >= 'A' && ch <= 'Z' ||
985 ch >= '0' && ch <= '9' ||
986 ch == ':' || ch == '-');
987 }
988
989 protected int skipWhitespace()
990 throws IOException
991 {
992 int ch = read();
993
994 for (;
995 ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
996 ch = read()) {
997 }
998
999 return ch;
1000 }
1001
1002 protected boolean isWhitespace(int ch)
1003 throws IOException
1004 {
1005 return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
1006 }
1007
1008 protected int read()
1009 throws IOException
1010 {
1011 if (peek > 0) {
1012 int value = peek;
1013 peek = 0;
1014 return value;
1015 }
1016
1017 return is.read();
1018 }
1019
1020 static {
1021 base64Decode = new int[256];
1022 for (int i = 'A'; i <= 'Z'; i++)
1023 base64Decode[i] = i - 'A';
1024 for (int i = 'a'; i <= 'z'; i++)
1025 base64Decode[i] = i - 'a' + 26;
1026 for (int i = '0'; i <= '9'; i++)
1027 base64Decode[i] = i - '0' + 52;
1028 base64Decode['+'] = 62;
1029 base64Decode['/'] = 63;
1030 }
1031 }