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 }