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: init(is);
103: }
104:
105: /**
106: * Creates an uninitialized Burlap input stream.
107: */
108: public MicroBurlapInput() {
109: }
110:
111: /**
112: * Returns a call's method.
113: */
114: public String getMethod() {
115: return method;
116: }
117:
118: /**
119: * Initialize the Burlap input stream with a new underlying stream.
120: * Applications can use <code>init(InputStream)</code> to reuse
121: * MicroBurlapInput to save garbage collection.
122: */
123: public void init(InputStream is) {
124: this .is = is;
125: this .refs = null;
126: }
127:
128: /**
129: * Starts reading the call
130: *
131: * <p>A successful completion will have a single value:
132: *
133: * <pre>
134: * <burlap:call>
135: * <method>method</method>
136: * </pre>
137: */
138: public void startCall() throws IOException {
139: expectStartTag("burlap:call");
140: expectStartTag("method");
141: method = parseString();
142: expectEndTag("method");
143: this .refs = null;
144: }
145:
146: /**
147: * Completes reading the call.
148: *
149: * <pre>
150: * </burlap:call>
151: * </pre>
152: */
153: public void completeCall() throws IOException {
154: expectEndTag("burlap:call");
155: }
156:
157: /**
158: * Reads a reply as an object.
159: * If the reply has a fault, throws the exception.
160: */
161: public Object readReply(Class expectedClass) throws Exception {
162: if (startReply()) {
163: Object value = readObject(expectedClass);
164: completeReply();
165: return value;
166: } else {
167: Hashtable fault = readFault();
168:
169: Object detail = fault.get("detail");
170: if (detail instanceof Exception)
171: throw (Exception) detail;
172:
173: else {
174: String code = (String) fault.get("code");
175: String message = (String) fault.get("message");
176:
177: throw new BurlapServiceException(message, code, detail);
178: }
179: }
180: }
181:
182: /**
183: * Starts reading the reply.
184: *
185: * <p>A successful completion will have a single value. An unsuccessful
186: * one will have a fault:
187: *
188: * <pre>
189: * <burlap:reply>
190: * </pre>
191: *
192: * @return true if success, false for fault.
193: */
194: public boolean startReply() throws IOException {
195: this .refs = null;
196:
197: expectStartTag("burlap:reply");
198:
199: if (!parseTag())
200: throw new BurlapProtocolException("expected <value>");
201:
202: String tag = sbuf.toString();
203: if (tag.equals("fault")) {
204: peekTag = true;
205: return false;
206: } else {
207: peekTag = true;
208: return true;
209: }
210: }
211:
212: /**
213: * Completes reading the reply.
214: *
215: * <pre>
216: * </burlap:reply>
217: * </pre>
218: */
219: public void completeReply() throws IOException {
220: expectEndTag("burlap:reply");
221: }
222:
223: /**
224: * Reads a boolean value from the input stream.
225: */
226: public boolean readBoolean() throws IOException {
227: expectStartTag("boolean");
228:
229: int value = parseInt();
230:
231: expectEndTag("boolean");
232:
233: return value != 0;
234: }
235:
236: /**
237: * Reads an integer value from the input stream.
238: */
239: public int readInt() throws IOException {
240: expectStartTag("int");
241:
242: int value = parseInt();
243:
244: expectEndTag("int");
245:
246: return value;
247: }
248:
249: /**
250: * Reads a long value from the input stream.
251: */
252: public long readLong() throws IOException {
253: expectStartTag("long");
254:
255: long value = parseLong();
256:
257: expectEndTag("long");
258:
259: return value;
260: }
261:
262: /**
263: * Reads a date value from the input stream.
264: */
265: public long readUTCDate() throws IOException {
266: expectStartTag("date");
267:
268: if (utcCalendar == null)
269: utcCalendar = Calendar.getInstance(TimeZone
270: .getTimeZone("UTC"));
271:
272: long value = parseDate(utcCalendar);
273:
274: expectEndTag("date");
275:
276: return value;
277: }
278:
279: /**
280: * Reads a date value from the input stream.
281: */
282: public long readLocalDate() throws IOException {
283: expectStartTag("date");
284:
285: if (localCalendar == null)
286: localCalendar = Calendar.getInstance();
287:
288: long value = parseDate(localCalendar);
289:
290: expectEndTag("date");
291:
292: return value;
293: }
294:
295: /**
296: * Reads a remote value from the input stream.
297: */
298: public BurlapRemote readRemote() throws IOException {
299: expectStartTag("remote");
300:
301: String type = readType();
302: String url = readString();
303:
304: expectEndTag("remote");
305:
306: return new BurlapRemote(type, url);
307: }
308:
309: /**
310: * Reads a string value from the input stream.
311: *
312: * <p>The two valid possibilities are either a <null>
313: * or a <string>. The string value is encoded in utf-8, and
314: * understands the basic XML escapes: "&123;", "<", ">",
315: * "'", """.
316: *
317: * <pre>
318: * <null></null>
319: * <string>a utf-8 encoded string</string>
320: * </pre>
321: */
322: public String readString() throws IOException {
323: if (!parseTag())
324: throw new BurlapProtocolException("expected <string>");
325:
326: String tag = sbuf.toString();
327: if (tag.equals("null")) {
328: expectEndTag("null");
329: return null;
330: } else if (tag.equals("string")) {
331: sbuf.setLength(0);
332: parseString(sbuf);
333: String value = sbuf.toString();
334: expectEndTag("string");
335: return value;
336: } else
337: throw expectBeginTag("string", tag);
338: }
339:
340: /**
341: * Reads a byte array from the input stream.
342: *
343: * <p>The two valid possibilities are either a <null>
344: * or a <base64>.
345: */
346: public byte[] readBytes() throws IOException {
347: if (!parseTag())
348: throw new BurlapProtocolException("expected <base64>");
349:
350: String tag = sbuf.toString();
351: if (tag.equals("null")) {
352: expectEndTag("null");
353: return null;
354: } else if (tag.equals("base64")) {
355: sbuf.setLength(0);
356: byte[] value = parseBytes();
357: expectEndTag("base64");
358: return value;
359: } else
360: throw expectBeginTag("base64", tag);
361: }
362:
363: /**
364: * Reads an arbitrary object the input stream.
365: */
366: public Object readObject(Class expectedClass) throws IOException {
367: if (!parseTag())
368: throw new BurlapProtocolException("expected <tag>");
369:
370: String tag = sbuf.toString();
371: if (tag.equals("null")) {
372: expectEndTag("null");
373: return null;
374: } else if (tag.equals("boolean")) {
375: int value = parseInt();
376: expectEndTag("boolean");
377: return new Boolean(value != 0);
378: } else if (tag.equals("int")) {
379: int value = parseInt();
380: expectEndTag("int");
381: return new Integer(value);
382: } else if (tag.equals("long")) {
383: long value = parseLong();
384: expectEndTag("long");
385: return new Long(value);
386: } else if (tag.equals("string")) {
387: sbuf.setLength(0);
388: parseString(sbuf);
389: String value = sbuf.toString();
390: expectEndTag("string");
391: return value;
392: } else if (tag.equals("xml")) {
393: sbuf.setLength(0);
394: parseString(sbuf);
395: String value = sbuf.toString();
396: expectEndTag("xml");
397: return value;
398: } else if (tag.equals("date")) {
399: if (utcCalendar == null)
400: utcCalendar = Calendar.getInstance(TimeZone
401: .getTimeZone("UTC"));
402:
403: long value = parseDate(utcCalendar);
404: expectEndTag("date");
405: return new Date(value);
406: } else if (tag.equals("map")) {
407: String type = readType();
408:
409: return readMap(expectedClass, type);
410: } else if (tag.equals("list")) {
411: String type = readType();
412:
413: int length = readLength();
414:
415: return readList(expectedClass, type, length);
416: } else if (tag.equals("ref")) {
417: int value = parseInt();
418: expectEndTag("ref");
419:
420: return refs.elementAt(value);
421: } else if (tag.equals("remote")) {
422: String type = readType();
423: String url = readString();
424:
425: expectEndTag("remote");
426:
427: return resolveRemote(type, url);
428: } else
429: return readExtensionObject(expectedClass, tag);
430: }
431:
432: /**
433: * Reads a type value from the input stream.
434: *
435: * <pre>
436: * <type>a utf-8 encoded string</type>
437: * </pre>
438: */
439: public String readType() throws IOException {
440: if (!parseTag())
441: throw new BurlapProtocolException("expected <type>");
442:
443: String tag = sbuf.toString();
444: if (!tag.equals("type"))
445: throw new BurlapProtocolException("expected <type>");
446:
447: sbuf.setLength(0);
448: parseString(sbuf);
449: String value = sbuf.toString();
450: expectEndTag("type");
451:
452: return value;
453: }
454:
455: /**
456: * Reads a length value from the input stream. If the length isn't
457: * specified, returns -1.
458: *
459: * <pre>
460: * <length>integer</length>
461: * </pre>
462: */
463: public int readLength() throws IOException {
464: expectStartTag("length");
465:
466: int ch = skipWhitespace();
467:
468: peek = ch;
469:
470: if (ch == '<') {
471: expectEndTag("length");
472: return -1;
473: }
474:
475: int value = parseInt();
476:
477: expectEndTag("length");
478:
479: return value;
480: }
481:
482: /**
483: * Resolves a remote object.
484: */
485: public Object resolveRemote(String type, String url)
486: throws IOException {
487: return new BurlapRemote(type, url);
488: }
489:
490: /**
491: * Reads a fault.
492: */
493: public Hashtable readFault() throws IOException {
494: expectStartTag("fault");
495:
496: Hashtable map = new Hashtable();
497:
498: while (parseTag()) {
499: peekTag = true;
500: Object key = readObject(null);
501: Object value = readObject(null);
502:
503: if (key != null && value != null)
504: map.put(key, value);
505: }
506:
507: if (!sbuf.toString().equals("fault"))
508: throw new BurlapProtocolException("expected </fault>");
509:
510: return map;
511: }
512:
513: /**
514: * Reads an object from the input stream.
515: *
516: * @param expectedClass the calling routine's expected class
517: * @param type the type from the stream
518: */
519: public Object readMap(Class expectedClass, String type)
520: throws IOException {
521: Hashtable map = new Hashtable();
522: if (refs == null)
523: refs = new Vector();
524: refs.addElement(map);
525:
526: while (parseTag()) {
527: peekTag = true;
528: Object key = readObject(null);
529: Object value = readObject(null);
530:
531: map.put(key, value);
532: }
533: if (!sbuf.toString().equals("map"))
534: throw new BurlapProtocolException("expected </map>");
535:
536: return map;
537: }
538:
539: /**
540: * Reads object unknown to MicroBurlapInput.
541: */
542: protected Object readExtensionObject(Class expectedClass, String tag)
543: throws IOException {
544: throw new BurlapProtocolException("unknown object tag <" + tag
545: + ">");
546: }
547:
548: /**
549: * Reads a list object from the input stream.
550: *
551: * @param expectedClass the calling routine's expected class
552: * @param type the type from the stream
553: * @param length the expected length, -1 for unspecified length
554: */
555: public Object readList(Class expectedClass, String type, int length)
556: throws IOException {
557: Vector list = new Vector();
558: if (refs == null)
559: refs = new Vector();
560: refs.addElement(list);
561:
562: while (parseTag()) {
563: peekTag = true;
564: Object value = readObject(null);
565:
566: list.addElement(value);
567: }
568:
569: if (!sbuf.toString().equals("list"))
570: throw new BurlapProtocolException("expected </list>");
571:
572: return list;
573: }
574:
575: /**
576: * Parses an integer value from the stream.
577: */
578: protected int parseInt() throws IOException {
579: int sign = 1;
580: int value = 0;
581:
582: int ch = skipWhitespace();
583: if (ch == '+')
584: ch = read();
585: else if (ch == '-') {
586: sign = -1;
587: ch = read();
588: }
589:
590: for (; ch >= '0' && ch <= '9'; ch = read())
591: value = 10 * value + ch - '0';
592:
593: peek = ch;
594:
595: return sign * value;
596: }
597:
598: /**
599: * Parses a long value from the stream.
600: */
601: protected long parseLong() throws IOException {
602: long sign = 1;
603: long value = 0;
604:
605: int ch = skipWhitespace();
606: if (ch == '+')
607: ch = read();
608: else if (ch == '-') {
609: sign = -1;
610: ch = read();
611: }
612:
613: for (; ch >= '0' && ch <= '9'; ch = read()) {
614: value = 10 * value + ch - '0';
615: }
616:
617: peek = ch;
618:
619: return sign * value;
620: }
621:
622: /**
623: * Parses a date value from the stream.
624: */
625: protected long parseDate(Calendar calendar) throws IOException {
626: int ch = skipWhitespace();
627:
628: int year = 0;
629: for (int i = 0; i < 4; i++) {
630: if (ch >= '0' && ch <= '9')
631: year = 10 * year + ch - '0';
632: else
633: throw expectedChar("year", ch);
634:
635: ch = read();
636: }
637:
638: int month = 0;
639: for (int i = 0; i < 2; i++) {
640: if (ch >= '0' && ch <= '9')
641: month = 10 * month + ch - '0';
642: else
643: throw expectedChar("month", ch);
644:
645: ch = read();
646: }
647:
648: int day = 0;
649: for (int i = 0; i < 2; i++) {
650: if (ch >= '0' && ch <= '9')
651: day = 10 * day + ch - '0';
652: else
653: throw expectedChar("day", ch);
654:
655: ch = read();
656: }
657:
658: if (ch != 'T')
659: throw expectedChar("`T'", ch);
660:
661: ch = read();
662:
663: int hour = 0;
664: for (int i = 0; i < 2; i++) {
665: if (ch >= '0' && ch <= '9')
666: hour = 10 * hour + ch - '0';
667: else
668: throw expectedChar("hour", ch);
669:
670: ch = read();
671: }
672:
673: int minute = 0;
674: for (int i = 0; i < 2; i++) {
675: if (ch >= '0' && ch <= '9')
676: minute = 10 * minute + ch - '0';
677: else
678: throw expectedChar("minute", ch);
679:
680: ch = read();
681: }
682:
683: int second = 0;
684: for (int i = 0; i < 2; i++) {
685: if (ch >= '0' && ch <= '9')
686: second = 10 * second + ch - '0';
687: else
688: throw expectedChar("second", ch);
689:
690: ch = read();
691: }
692:
693: for (; ch > 0 && ch != '<'; ch = read()) {
694: }
695:
696: peek = ch;
697:
698: calendar.set(Calendar.YEAR, year);
699: calendar.set(Calendar.MONTH, month - 1);
700: calendar.set(Calendar.DAY_OF_MONTH, day);
701: calendar.set(Calendar.HOUR_OF_DAY, hour);
702: calendar.set(Calendar.MINUTE, minute);
703: calendar.set(Calendar.SECOND, second);
704: calendar.set(Calendar.MILLISECOND, 0);
705:
706: return calendar.getTime().getTime();
707: }
708:
709: /**
710: * Parses a string value from the stream.
711: * string buffer is used for the result.
712: */
713: protected String parseString() throws IOException {
714: StringBuffer sbuf = new StringBuffer();
715:
716: return parseString(sbuf).toString();
717: }
718:
719: /**
720: * Parses a string value from the stream. The burlap object's
721: * string buffer is used for the result.
722: */
723: protected StringBuffer parseString(StringBuffer sbuf)
724: throws IOException {
725: int ch = read();
726:
727: for (; ch >= 0 && ch != '<'; ch = read()) {
728: if (ch == '&') {
729: ch = read();
730:
731: if (ch == '#') {
732: ch = read();
733:
734: if (ch >= '0' && ch <= '9') {
735: int v = 0;
736: for (; ch >= '0' && ch <= '9'; ch = read()) {
737: v = 10 * v + ch - '0';
738: }
739:
740: sbuf.append((char) v);
741: }
742: } else {
743: StringBuffer entityBuffer = new StringBuffer();
744:
745: for (; ch >= 'a' && ch <= 'z'; ch = read())
746: entityBuffer.append((char) ch);
747:
748: String entity = entityBuffer.toString();
749: if (entity.equals("amp"))
750: sbuf.append('&');
751: else if (entity.equals("apos"))
752: sbuf.append('\'');
753: else if (entity.equals("quot"))
754: sbuf.append('"');
755: else if (entity.equals("lt"))
756: sbuf.append('<');
757: else if (entity.equals("gt"))
758: sbuf.append('>');
759: else
760: throw new BurlapProtocolException(
761: "unknown XML entity &" + entity
762: + "; at `" + (char) ch + "'");
763: }
764:
765: if (ch != ';')
766: throw expectedChar("';'", ch);
767: } else if (ch < 0x80)
768: sbuf.append((char) ch);
769: else if ((ch & 0xe0) == 0xc0) {
770: int ch1 = read();
771: int v = ((ch & 0x1f) << 6) + (ch1 & 0x3f);
772:
773: sbuf.append((char) v);
774: } else if ((ch & 0xf0) == 0xe0) {
775: int ch1 = read();
776: int ch2 = read();
777: int v = ((ch & 0x0f) << 12) + ((ch1 & 0x3f) << 6)
778: + (ch2 & 0x3f);
779:
780: sbuf.append((char) v);
781: } else
782: throw new BurlapProtocolException("bad utf-8 encoding");
783: }
784:
785: peek = ch;
786:
787: return sbuf;
788: }
789:
790: /**
791: * Parses a byte array.
792: */
793: protected byte[] parseBytes() throws IOException {
794: ByteArrayOutputStream bos = new ByteArrayOutputStream();
795:
796: parseBytes(bos);
797:
798: return bos.toByteArray();
799: }
800:
801: /**
802: * Parses a byte array.
803: */
804: protected ByteArrayOutputStream parseBytes(ByteArrayOutputStream bos)
805: throws IOException {
806: int ch;
807: for (ch = read(); ch >= 0 && ch != '<'; ch = read()) {
808: int b1 = ch;
809: int b2 = read();
810: int b3 = read();
811: int b4 = read();
812:
813: if (b4 != '=') {
814: int chunk = ((base64Decode[b1] << 18)
815: + (base64Decode[b2] << 12)
816: + (base64Decode[b3] << 6) + (base64Decode[b4]));
817:
818: bos.write(chunk >> 16);
819: bos.write(chunk >> 8);
820: bos.write(chunk);
821: } else if (b3 != '=') {
822: int chunk = ((base64Decode[b1] << 12)
823: + (base64Decode[b2] << 6) + (base64Decode[b3]));
824:
825: bos.write(chunk >> 8);
826: bos.write(chunk);
827: } else {
828: int chunk = ((base64Decode[b1] << 6) + (base64Decode[b2]));
829:
830: bos.write(chunk);
831: }
832: }
833:
834: if (ch == '<')
835: peek = ch;
836:
837: return bos;
838: }
839:
840: protected void expectStartTag(String tag) throws IOException {
841: if (!parseTag())
842: throw new BurlapProtocolException("expected <" + tag + ">");
843:
844: if (!sbuf.toString().equals(tag))
845: throw new BurlapProtocolException("expected <" + tag
846: + "> at <" + sbuf + ">");
847: }
848:
849: protected void expectEndTag(String tag) throws IOException {
850: if (parseTag())
851: throw new BurlapProtocolException("expected </" + tag + ">");
852:
853: if (!sbuf.toString().equals(tag))
854: throw new BurlapProtocolException("expected </" + tag
855: + "> at </" + sbuf + ">");
856: }
857:
858: /**
859: * Parses a tag. Returns true if it's a start tag.
860: */
861: protected boolean parseTag() throws IOException {
862: if (peekTag) {
863: peekTag = false;
864: return true;
865: }
866:
867: int ch = skipWhitespace();
868: boolean isStartTag = true;
869:
870: if (ch != '<')
871: throw expectedChar("'<'", ch);
872:
873: ch = read();
874: if (ch == '/') {
875: isStartTag = false;
876: ch = is.read();
877: }
878:
879: if (!isTagChar(ch))
880: throw expectedChar("tag", ch);
881:
882: sbuf.setLength(0);
883: for (; isTagChar(ch); ch = read())
884: sbuf.append((char) ch);
885:
886: if (ch != '>')
887: throw expectedChar("'>'", ch);
888:
889: return isStartTag;
890: }
891:
892: protected IOException expectedChar(String expect, int actualChar) {
893: return new BurlapProtocolException("expected " + expect
894: + " at " + (char) actualChar + "'");
895: }
896:
897: protected IOException expectBeginTag(String expect, String tag) {
898: return new BurlapProtocolException("expected <" + expect
899: + "> at <" + tag + ">");
900: }
901:
902: private boolean isTagChar(int ch) {
903: return (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z'
904: || ch >= '0' && ch <= '9' || ch == ':' || ch == '-');
905: }
906:
907: protected int skipWhitespace() throws IOException {
908: int ch = read();
909:
910: for (; ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'; ch = read()) {
911: }
912:
913: return ch;
914: }
915:
916: protected boolean isWhitespace(int ch) throws IOException {
917: return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
918: }
919:
920: protected int read() throws IOException {
921: if (peek > 0) {
922: int value = peek;
923: peek = 0;
924: return value;
925: }
926:
927: return is.read();
928: }
929:
930: static {
931: base64Decode = new int[256];
932: for (int i = 'A'; i <= 'Z'; i++)
933: base64Decode[i] = i - 'A';
934: for (int i = 'a'; i <= 'z'; i++)
935: base64Decode[i] = i - 'a' + 26;
936: for (int i = '0'; i <= '9'; i++)
937: base64Decode[i] = i - '0' + 52;
938: base64Decode['+'] = 62;
939: base64Decode['/'] = 63;
940: }
941: }
|