1 /*
2  * Copyright (c) 2007, 2018 Oracle and/or its affiliates. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Distribution License v. 1.0, which is available at
6  * http://www.eclipse.org/org/documents/edl-v10.php.
7  *
8  * SPDX-License-Identifier: BSD-3-Clause
9  */

10
11 package javax.xml.bind;
12
13 import java.math.BigDecimal;
14 import java.math.BigInteger;
15 import java.util.Calendar;
16 import java.util.GregorianCalendar;
17 import java.util.TimeZone;
18
19 import javax.xml.namespace.QName;
20 import javax.xml.namespace.NamespaceContext;
21 import javax.xml.datatype.DatatypeFactory;
22 import javax.xml.datatype.DatatypeConfigurationException;
23
24 /**
25  * This class is the JAXB RI's default implementation of the
26  * {@link DatatypeConverterInterface}.
27  *
28  * <p>
29  * When client applications specify the use of the static print/parse
30  * methods in {@link DatatypeConverter}, it will delegate
31  * to this class.
32  *
33  * <p>
34  * This class is responsible for whitespace normalization.
35  *
36  * @author <ul><li>Ryan Shoemaker, Sun Microsystems, Inc.</li></ul>
37  * @since JAXB 2.1
38  */

39 final class DatatypeConverterImpl implements DatatypeConverterInterface {
40
41     /**
42      * To avoid re-creating instances, we cache one instance.
43      */

44     public static final DatatypeConverterInterface theInstance = new DatatypeConverterImpl();
45
46     protected DatatypeConverterImpl() {
47     }
48
49     public String parseString(String lexicalXSDString) {
50         return lexicalXSDString;
51     }
52
53     public BigInteger parseInteger(String lexicalXSDInteger) {
54         return _parseInteger(lexicalXSDInteger);
55     }
56
57     public static BigInteger _parseInteger(CharSequence s) {
58         return new BigInteger(removeOptionalPlus(WhiteSpaceProcessor.trim(s)).toString());
59     }
60
61     public String printInteger(BigInteger val) {
62         return _printInteger(val);
63     }
64
65     public static String _printInteger(BigInteger val) {
66         return val.toString();
67     }
68
69     public int parseInt(String s) {
70         return _parseInt(s);
71     }
72
73     /**
74      * Faster but less robust String->int conversion.
75      *
76      * Note that:
77      * <ol>
78      *  <li>XML Schema allows '+', but {@link Integer#valueOf(String)} is not.
79      *  <li>XML Schema allows leading and trailing (but not in-between) whitespaces.
80      *      {@link Integer#valueOf(String)} doesn't allow any.
81      * </ol>
82      */

83     public static int _parseInt(CharSequence s) {
84         int len = s.length();
85         int sign = 1;
86
87         int r = 0;
88
89         for (int i = 0; i < len; i++) {
90             char ch = s.charAt(i);
91             if (WhiteSpaceProcessor.isWhiteSpace(ch)) {
92                 // skip whitespace
93             } else if ('0' <= ch && ch <= '9') {
94                 r = r * 10 + (ch - '0');
95             } else if (ch == '-') {
96                 sign = -1;
97             } else if (ch == '+') {
98                 // noop
99             } else {
100                 throw new NumberFormatException("Not a number: " + s);
101             }
102         }
103
104         return r * sign;
105     }
106
107     public long parseLong(String lexicalXSLong) {
108         return _parseLong(lexicalXSLong);
109     }
110
111     public static long _parseLong(CharSequence s) {
112         return Long.parseLong(removeOptionalPlus(WhiteSpaceProcessor.trim(s)).toString());
113     }
114
115     public short parseShort(String lexicalXSDShort) {
116         return _parseShort(lexicalXSDShort);
117     }
118
119     public static short _parseShort(CharSequence s) {
120         return (short) _parseInt(s);
121     }
122
123     public String printShort(short val) {
124         return _printShort(val);
125     }
126
127     public static String _printShort(short val) {
128         return String.valueOf(val);
129     }
130
131     public BigDecimal parseDecimal(String content) {
132         return _parseDecimal(content);
133     }
134
135     public static BigDecimal _parseDecimal(CharSequence content) {
136         content = WhiteSpaceProcessor.trim(content);
137
138         if (content.length() <= 0) {
139             return null;
140         }
141
142         return new BigDecimal(content.toString());
143
144         // from purely XML Schema perspective,
145         // this implementation has a problem, since
146         // in xs:decimal "1.0" and "1" is equal whereas the above
147         // code will return different values for those two forms.
148         //
149         // the code was originally using com.sun.msv.datatype.xsd.NumberType.load,
150         // but a profiling showed that the process of normalizing "1.0" into "1"
151         // could take non-trivial time.
152         //
153         // also, from the user's point of view, one might be surprised if
154         // 1 (not 1.0) is returned from "1.000"
155     }
156
157     public float parseFloat(String lexicalXSDFloat) {
158         return _parseFloat(lexicalXSDFloat);
159     }
160
161     public static float _parseFloat(CharSequence _val) {
162         String s = WhiteSpaceProcessor.trim(_val).toString();
163         /* Incompatibilities of XML Schema's float "xfloat" and Java's float "jfloat"
164
165          * jfloat.valueOf ignores leading and trailing whitespaces,
166         whereas this is not allowed in xfloat.
167          * jfloat.valueOf allows "float type suffix" (f, F) to be
168         appended after float literal (e.g., 1.52e-2f), whereare
169         this is not the case of xfloat.
170
171         gray zone
172         ---------
173          * jfloat allows ".523". And there is no clear statement that mentions
174         this case in xfloat. Although probably this is allowed.
175          *
176          */

177
178         if (s.equals("NaN")) {
179             return Float.NaN;
180         }
181         if (s.equals("INF")) {
182             return Float.POSITIVE_INFINITY;
183         }
184         if (s.equals("-INF")) {
185             return Float.NEGATIVE_INFINITY;
186         }
187
188         if (s.length() == 0
189                 || !isDigitOrPeriodOrSign(s.charAt(0))
190                 || !isDigitOrPeriodOrSign(s.charAt(s.length() - 1))) {
191             throw new NumberFormatException();
192         }
193
194         // these screening process is necessary due to the wobble of Float.valueOf method
195         return Float.parseFloat(s);
196     }
197
198     public String printFloat(float v) {
199         return _printFloat(v);
200     }
201
202     public static String _printFloat(float v) {
203         if (Float.isNaN(v)) {
204             return "NaN";
205         }
206         if (v == Float.POSITIVE_INFINITY) {
207             return "INF";
208         }
209         if (v == Float.NEGATIVE_INFINITY) {
210             return "-INF";
211         }
212         return String.valueOf(v);
213     }
214
215     public double parseDouble(String lexicalXSDDouble) {
216         return _parseDouble(lexicalXSDDouble);
217     }
218
219     public static double _parseDouble(CharSequence _val) {
220         String val = WhiteSpaceProcessor.trim(_val).toString();
221
222         if (val.equals("NaN")) {
223             return Double.NaN;
224         }
225         if (val.equals("INF")) {
226             return Double.POSITIVE_INFINITY;
227         }
228         if (val.equals("-INF")) {
229             return Double.NEGATIVE_INFINITY;
230         }
231
232         if (val.length() == 0
233                 || !isDigitOrPeriodOrSign(val.charAt(0))
234                 || !isDigitOrPeriodOrSign(val.charAt(val.length() - 1))) {
235             throw new NumberFormatException(val);
236         }
237
238
239         // these screening process is necessary due to the wobble of Float.valueOf method
240         return Double.parseDouble(val);
241     }
242
243     public boolean parseBoolean(String lexicalXSDBoolean) {
244         Boolean b = _parseBoolean(lexicalXSDBoolean);
245         return (b == null) ? false : b.booleanValue();
246     }
247
248     public static Boolean _parseBoolean(CharSequence literal) {
249         if (literal == null) {
250             return null;
251         }
252
253         int i = 0;
254         int len = literal.length();
255         char ch;
256         boolean value = false;
257
258         if (literal.length() <= 0) {
259             return null;
260         }
261
262         do {
263             ch = literal.charAt(i++);
264         } while (WhiteSpaceProcessor.isWhiteSpace(ch) && i < len);
265
266         int strIndex = 0;
267
268         switch (ch) {
269             case '1':
270                 value = true;
271                 break;
272             case '0':
273                 value = false;
274                 break;
275             case 't':
276                 String strTrue = "rue";
277                 do {
278                     ch = literal.charAt(i++);
279                 } while ((strTrue.charAt(strIndex++) == ch) && i < len && strIndex < 3);
280
281                 if (strIndex == 3) {
282                     value = true;
283                 } else {
284                     return false;
285                 }
286 //                    throw new IllegalArgumentException("String \"" + literal + "\" is not valid boolean value.");
287
288                 break;
289             case 'f':
290                 String strFalse = "alse";
291                 do {
292                     ch = literal.charAt(i++);
293                 } while ((strFalse.charAt(strIndex++) == ch) && i < len && strIndex < 4);
294
295
296                 if (strIndex == 4) {
297                     value = false;
298                 } else {
299                     return false;
300                 }
301 //                    throw new IllegalArgumentException("String \"" + literal + "\" is not valid boolean value.");
302
303                 break;
304         }
305
306         if (i < len) {
307             do {
308                 ch = literal.charAt(i++);
309             } while (WhiteSpaceProcessor.isWhiteSpace(ch) && i < len);
310         }
311
312         if (i == len) {
313             return value;
314         } else {
315             return null;
316         }
317 //            throw new IllegalArgumentException("String \"" + literal + "\" is not valid boolean value.");
318     }
319
320     public String printBoolean(boolean val) {
321         return val ? "true" : "false";
322     }
323
324     public static String _printBoolean(boolean val) {
325         return val ? "true" : "false";
326     }
327
328     public byte parseByte(String lexicalXSDByte) {
329         return _parseByte(lexicalXSDByte);
330     }
331
332     public static byte _parseByte(CharSequence literal) {
333         return (byte) _parseInt(literal);
334     }
335
336     public String printByte(byte val) {
337         return _printByte(val);
338     }
339
340     public static String _printByte(byte val) {
341         return String.valueOf(val);
342     }
343
344     public QName parseQName(String lexicalXSDQName, NamespaceContext nsc) {
345         return _parseQName(lexicalXSDQName, nsc);
346     }
347
348     /**
349      * @return null if fails to convert.
350      */

351     public static QName _parseQName(CharSequence text, NamespaceContext nsc) {
352         int length = text.length();
353
354         // trim whitespace
355         int start = 0;
356         while (start < length && WhiteSpaceProcessor.isWhiteSpace(text.charAt(start))) {
357             start++;
358         }
359
360         int end = length;
361         while (end > start && WhiteSpaceProcessor.isWhiteSpace(text.charAt(end - 1))) {
362             end--;
363         }
364
365         if (end == start) {
366             throw new IllegalArgumentException("input is empty");
367         }
368
369
370         String uri;
371         String localPart;
372         String prefix;
373
374         // search ':'
375         int idx = start + 1;    // no point in searching the first char. that's not valid.
376         while (idx < end && text.charAt(idx) != ':') {
377             idx++;
378         }
379
380         if (idx == end) {
381             uri = nsc.getNamespaceURI("");
382             localPart = text.subSequence(start, end).toString();
383             prefix = "";
384         } else {
385             // Prefix exists, check everything
386             prefix = text.subSequence(start, idx).toString();
387             localPart = text.subSequence(idx + 1, end).toString();
388             uri = nsc.getNamespaceURI(prefix);
389             // uri can never be null according to javadoc,
390             // but some users reported that there are implementations that return null.
391             if (uri == null || uri.length() == 0) // crap. the NamespaceContext interface is broken.
392             // error: unbound prefix
393             {
394                 throw new IllegalArgumentException("prefix " + prefix + " is not bound to a namespace");
395             }
396         }
397
398         return new QName(uri, localPart, prefix);
399     }
400
401     public Calendar parseDateTime(String lexicalXSDDateTime) {
402         return _parseDateTime(lexicalXSDDateTime);
403     }
404
405     public static GregorianCalendar _parseDateTime(CharSequence s) {
406         String val = WhiteSpaceProcessor.trim(s).toString();
407         return datatypeFactory.newXMLGregorianCalendar(val).toGregorianCalendar();
408     }
409
410     public String printDateTime(Calendar val) {
411         return _printDateTime(val);
412     }
413
414     public static String _printDateTime(Calendar val) {
415         return CalendarFormatter.doFormat("%Y-%M-%DT%h:%m:%s%z", val);
416     }
417
418     public byte[] parseBase64Binary(String lexicalXSDBase64Binary) {
419         return _parseBase64Binary(lexicalXSDBase64Binary);
420     }
421
422     public byte[] parseHexBinary(String s) {
423         final int len = s.length();
424
425         // "111" is not a valid hex encoding.
426         if (len % 2 != 0) {
427             throw new IllegalArgumentException("hexBinary needs to be even-length: " + s);
428         }
429
430         byte[] out = new byte[len / 2];
431
432         for (int i = 0; i < len; i += 2) {
433             int h = hexToBin(s.charAt(i));
434             int l = hexToBin(s.charAt(i + 1));
435             if (h == -1 || l == -1) {
436                 throw new IllegalArgumentException("contains illegal character for hexBinary: " + s);
437             }
438
439             out[i / 2] = (byte) (h * 16 + l);
440         }
441
442         return out;
443     }
444
445     private static int hexToBin(char ch) {
446         if ('0' <= ch && ch <= '9') {
447             return ch - '0';
448         }
449         if ('A' <= ch && ch <= 'F') {
450             return ch - 'A' + 10;
451         }
452         if ('a' <= ch && ch <= 'f') {
453             return ch - 'a' + 10;
454         }
455         return -1;
456     }
457     private static final char[] hexCode = "0123456789ABCDEF".toCharArray();
458
459     public String printHexBinary(byte[] data) {
460         StringBuilder r = new StringBuilder(data.length * 2);
461         for (byte b : data) {
462             r.append(hexCode[(b >> 4) & 0xF]);
463             r.append(hexCode[(b & 0xF)]);
464         }
465         return r.toString();
466     }
467
468     public long parseUnsignedInt(String lexicalXSDUnsignedInt) {
469         return _parseLong(lexicalXSDUnsignedInt);
470     }
471
472     public String printUnsignedInt(long val) {
473         return _printLong(val);
474     }
475
476     public int parseUnsignedShort(String lexicalXSDUnsignedShort) {
477         return _parseInt(lexicalXSDUnsignedShort);
478     }
479
480     public Calendar parseTime(String lexicalXSDTime) {
481         return datatypeFactory.newXMLGregorianCalendar(lexicalXSDTime).toGregorianCalendar();
482     }
483
484     public String printTime(Calendar val) {
485         return CalendarFormatter.doFormat("%h:%m:%s%z", val);
486     }
487
488     public Calendar parseDate(String lexicalXSDDate) {
489         return datatypeFactory.newXMLGregorianCalendar(lexicalXSDDate).toGregorianCalendar();
490     }
491
492     public String printDate(Calendar val) {
493         return _printDate(val);
494     }
495
496     public static String _printDate(Calendar val) {
497         return CalendarFormatter.doFormat((new StringBuilder("%Y-%M-%D").append("%z")).toString(),val);
498     }
499
500     public String parseAnySimpleType(String lexicalXSDAnySimpleType) {
501         return lexicalXSDAnySimpleType;
502 //        return (String)SimpleURType.theInstance._createValue( lexicalXSDAnySimpleType, null );
503     }
504
505     public String printString(String val) {
506 //        return StringType.theInstance.convertToLexicalValue( val, null );
507         return val;
508     }
509
510     public String printInt(int val) {
511         return _printInt(val);
512     }
513
514     public static String _printInt(int val) {
515         return String.valueOf(val);
516     }
517
518     public String printLong(long val) {
519         return _printLong(val);
520     }
521
522     public static String _printLong(long val) {
523         return String.valueOf(val);
524     }
525
526     public String printDecimal(BigDecimal val) {
527         return _printDecimal(val);
528     }
529
530     public static String _printDecimal(BigDecimal val) {
531         return val.toPlainString();
532     }
533
534     public String printDouble(double v) {
535         return _printDouble(v);
536     }
537
538     public static String _printDouble(double v) {
539         if (Double.isNaN(v)) {
540             return "NaN";
541         }
542         if (v == Double.POSITIVE_INFINITY) {
543             return "INF";
544         }
545         if (v == Double.NEGATIVE_INFINITY) {
546             return "-INF";
547         }
548         return String.valueOf(v);
549     }
550
551     public String printQName(QName val, NamespaceContext nsc) {
552         return _printQName(val, nsc);
553     }
554
555     public static String _printQName(QName val, NamespaceContext nsc) {
556         // Double-check
557         String qname;
558         String prefix = nsc.getPrefix(val.getNamespaceURI());
559         String localPart = val.getLocalPart();
560
561         if (prefix == null || prefix.length() == 0) { // be defensive
562             qname = localPart;
563         } else {
564             qname = prefix + ':' + localPart;
565         }
566
567         return qname;
568     }
569
570     public String printBase64Binary(byte[] val) {
571         return _printBase64Binary(val);
572     }
573
574     public String printUnsignedShort(int val) {
575         return String.valueOf(val);
576     }
577
578     public String printAnySimpleType(String val) {
579         return val;
580     }
581
582     /**
583      * Just return the string passed as a parameter but
584      * installs an instance of this class as the DatatypeConverter
585      * implementation. Used from static fixed value initializers.
586      */

587     public static String installHook(String s) {
588         DatatypeConverter.setDatatypeConverter(theInstance);
589         return s;
590     }
591 // base64 decoder
592     private static final byte[] decodeMap = initDecodeMap();
593     private static final byte PADDING = 127;
594
595     private static byte[] initDecodeMap() {
596         byte[] map = new byte[128];
597         int i;
598         for (i = 0; i < 128; i++) {
599             map[i] = -1;
600         }
601
602         for (i = 'A'; i <= 'Z'; i++) {
603             map[i] = (byte) (i - 'A');
604         }
605         for (i = 'a'; i <= 'z'; i++) {
606             map[i] = (byte) (i - 'a' + 26);
607         }
608         for (i = '0'; i <= '9'; i++) {
609             map[i] = (byte) (i - '0' + 52);
610         }
611         map['+'] = 62;
612         map['/'] = 63;
613         map['='] = PADDING;
614
615         return map;
616     }
617
618     /**
619      * computes the length of binary data speculatively.
620      *
621      * <p>
622      * Our requirement is to create byte[] of the exact length to store the binary data.
623      * If we do this in a straight-forward way, it takes two passes over the data.
624      * Experiments show that this is a non-trivial overhead (35% or so is spent on
625      * the first pass in calculating the length.)
626      *
627      * <p>
628      * So the approach here is that we compute the length speculatively, without looking
629      * at the whole contents. The obtained speculative value is never less than the
630      * actual length of the binary data, but it may be bigger. So if the speculation
631      * goes wrong, we'll pay the cost of reallocation and buffer copying.
632      *
633      * <p>
634      * If the base64 text is tightly packed with no indentation nor illegal char
635      * (like what most web services produce), then the speculation of this method
636      * will be correct, so we get the performance benefit.
637      */

638     private static int guessLength(String text) {
639         final int len = text.length();
640
641         // compute the tail '=' chars
642         int j = len - 1;
643         for (; j >= 0; j--) {
644             byte code = decodeMap[text.charAt(j)];
645             if (code == PADDING) {
646                 continue;
647             }
648             if (code == -1) // most likely this base64 text is indented. go with the upper bound
649             {
650                 return text.length() / 4 * 3;
651             }
652             break;
653         }
654
655         j++;    // text.charAt(j) is now at some base64 char, so +1 to make it the size
656         int padSize = len - j;
657         if (padSize > 2) // something is wrong with base64. be safe and go with the upper bound
658         {
659             return text.length() / 4 * 3;
660         }
661
662         // so far this base64 looks like it's unindented tightly packed base64.
663         // take a chance and create an array with the expected size
664         return text.length() / 4 * 3 - padSize;
665     }
666
667     /**
668      * @param text
669      *      base64Binary data is likely to be long, and decoding requires
670      *      each character to be accessed twice (once for counting length, another
671      *      for decoding.)
672      *
673      *      A benchmark showed that taking {@link String} is faster, presumably
674      *      because JIT can inline a lot of string access (with data of 1K chars, it was twice as fast)
675      */

676     public static byte[] _parseBase64Binary(String text) {
677         final int buflen = guessLength(text);
678         final byte[] out = new byte[buflen];
679         int o = 0;
680
681         final int len = text.length();
682         int i;
683
684         final byte[] quadruplet = new byte[4];
685         int q = 0;
686
687         // convert each quadruplet to three bytes.
688         for (i = 0; i < len; i++) {
689             char ch = text.charAt(i);
690             byte v = decodeMap[ch];
691
692             if (v != -1) {
693                 quadruplet[q++] = v;
694             }
695
696             if (q == 4) {
697                 // quadruplet is now filled.
698                 out[o++] = (byte) ((quadruplet[0] << 2) | (quadruplet[1] >> 4));
699                 if (quadruplet[2] != PADDING) {
700                     out[o++] = (byte) ((quadruplet[1] << 4) | (quadruplet[2] >> 2));
701                 }
702                 if (quadruplet[3] != PADDING) {
703                     out[o++] = (byte) ((quadruplet[2] << 6) | (quadruplet[3]));
704                 }
705                 q = 0;
706             }
707         }
708
709         if (buflen == o) // speculation worked out to be OK
710         {
711             return out;
712         }
713
714         // we overestimated, so need to create a new buffer
715         byte[] nb = new byte[o];
716         System.arraycopy(out, 0, nb, 0, o);
717         return nb;
718     }
719     private static final char[] encodeMap = initEncodeMap();
720
721     private static char[] initEncodeMap() {
722         char[] map = new char[64];
723         int i;
724         for (i = 0; i < 26; i++) {
725             map[i] = (char) ('A' + i);
726         }
727         for (i = 26; i < 52; i++) {
728             map[i] = (char) ('a' + (i - 26));
729         }
730         for (i = 52; i < 62; i++) {
731             map[i] = (char) ('0' + (i - 52));
732         }
733         map[62] = '+';
734         map[63] = '/';
735
736         return map;
737     }
738
739     public static char encode(int i) {
740         return encodeMap[i & 0x3F];
741     }
742
743     public static byte encodeByte(int i) {
744         return (byte) encodeMap[i & 0x3F];
745     }
746
747     public static String _printBase64Binary(byte[] input) {
748         return _printBase64Binary(input, 0, input.length);
749     }
750
751     public static String _printBase64Binary(byte[] input, int offset, int len) {
752         char[] buf = new char[((len + 2) / 3) * 4];
753         int ptr = _printBase64Binary(input, offset, len, buf, 0);
754         assert ptr == buf.length;
755         return new String(buf);
756     }
757
758     /**
759      * Encodes a byte array into a char array by doing base64 encoding.
760      *
761      * The caller must supply a big enough buffer.
762      *
763      * @return
764      *      the value of {@code ptr+((len+2)/3)*4}, which is the new offset
765      *      in the output buffer where the further bytes should be placed.
766      */

767     public static int _printBase64Binary(byte[] input, int offset, int len, char[] buf, int ptr) {
768         // encode elements until only 1 or 2 elements are left to encode
769         int remaining = len;
770         int i;
771         for (i = offset;remaining >= 3; remaining -= 3, i += 3) {
772             buf[ptr++] = encode(input[i] >> 2);
773             buf[ptr++] = encode(
774                     ((input[i] & 0x3) << 4)
775                     | ((input[i + 1] >> 4) & 0xF));
776             buf[ptr++] = encode(
777                     ((input[i + 1] & 0xF) << 2)
778                     | ((input[i + 2] >> 6) & 0x3));
779             buf[ptr++] = encode(input[i + 2] & 0x3F);
780         }
781         // encode when exactly 1 element (left) to encode
782         if (remaining == 1) {
783             buf[ptr++] = encode(input[i] >> 2);
784             buf[ptr++] = encode(((input[i]) & 0x3) << 4);
785             buf[ptr++] = '=';
786             buf[ptr++] = '=';
787         }
788         // encode when exactly 2 elements (left) to encode
789         if (remaining == 2) {
790             buf[ptr++] = encode(input[i] >> 2);
791             buf[ptr++] = encode(((input[i] & 0x3) << 4)
792                     | ((input[i + 1] >> 4) & 0xF));
793             buf[ptr++] = encode((input[i + 1] & 0xF) << 2);
794             buf[ptr++] = '=';
795         }
796         return ptr;
797     }
798
799     /**
800      * Encodes a byte array into another byte array by first doing base64 encoding
801      * then encoding the result in ASCII.
802      *
803      * The caller must supply a big enough buffer.
804      *
805      * @return
806      *      the value of {@code ptr+((len+2)/3)*4}, which is the new offset
807      *      in the output buffer where the further bytes should be placed.
808      */

809     public static int _printBase64Binary(byte[] input, int offset, int len, byte[] out, int ptr) {
810         byte[] buf = out;
811         int remaining = len;
812         int i;
813         for (i=offset; remaining >= 3; remaining -= 3, i += 3 ) {
814             buf[ptr++] = encodeByte(input[i]>>2);
815             buf[ptr++] = encodeByte(
816                         ((input[i]&0x3)<<4) |
817                         ((input[i+1]>>4)&0xF));
818             buf[ptr++] = encodeByte(
819                         ((input[i+1]&0xF)<<2)|
820                         ((input[i+2]>>6)&0x3));
821             buf[ptr++] = encodeByte(input[i+2]&0x3F);
822         }
823         // encode when exactly 1 element (left) to encode
824         if (remaining == 1) {
825             buf[ptr++] = encodeByte(input[i]>>2);
826             buf[ptr++] = encodeByte(((input[i])&0x3)<<4);
827             buf[ptr++] = '=';
828             buf[ptr++] = '=';
829         }
830         // encode when exactly 2 elements (left) to encode
831         if (remaining == 2) {
832             buf[ptr++] = encodeByte(input[i]>>2);
833             buf[ptr++] = encodeByte(
834                         ((input[i]&0x3)<<4) |
835                         ((input[i+1]>>4)&0xF));
836             buf[ptr++] = encodeByte((input[i+1]&0xF)<<2);
837             buf[ptr++] = '=';
838         }
839
840         return ptr;
841     }
842
843     private static CharSequence removeOptionalPlus(CharSequence s) {
844         int len = s.length();
845
846         if (len <= 1 || s.charAt(0) != '+') {
847             return s;
848         }
849
850         s = s.subSequence(1, len);
851         char ch = s.charAt(0);
852         if ('0' <= ch && ch <= '9') {
853             return s;
854         }
855         if ('.' == ch) {
856             return s;
857         }
858
859         throw new NumberFormatException();
860     }
861
862     private static boolean isDigitOrPeriodOrSign(char ch) {
863         if ('0' <= ch && ch <= '9') {
864             return true;
865         }
866         if (ch == '+' || ch == '-' || ch == '.') {
867             return true;
868         }
869         return false;
870     }
871     private static final DatatypeFactory datatypeFactory;
872
873     static {
874         try {
875             datatypeFactory = DatatypeFactory.newInstance();
876         } catch (DatatypeConfigurationException e) {
877             throw new Error(e);
878         }
879     }
880
881     private static final class CalendarFormatter {
882
883         public static String doFormat(String format, Calendar cal) throws IllegalArgumentException {
884             int fidx = 0;
885             int flen = format.length();
886             StringBuilder buf = new StringBuilder();
887
888             while (fidx < flen) {
889                 char fch = format.charAt(fidx++);
890
891                 if (fch != '%') {  // not a meta character
892                     buf.append(fch);
893                     continue;
894                 }
895
896                 // seen meta character. we don't do error check against the format
897                 switch (format.charAt(fidx++)) {
898                     case 'Y': // year
899                         formatYear(cal, buf);
900                         break;
901
902                     case 'M': // month
903                         formatMonth(cal, buf);
904                         break;
905
906                     case 'D': // days
907                         formatDays(cal, buf);
908                         break;
909
910                     case 'h': // hours
911                         formatHours(cal, buf);
912                         break;
913
914                     case 'm': // minutes
915                         formatMinutes(cal, buf);
916                         break;
917
918                     case 's': // parse seconds.
919                         formatSeconds(cal, buf);
920                         break;
921
922                     case 'z': // time zone
923                         formatTimeZone(cal, buf);
924                         break;
925
926                     default:
927                         // illegal meta character. impossible.
928                         throw new InternalError();
929                 }
930             }
931
932             return buf.toString();
933         }
934
935         private static void formatYear(Calendar cal, StringBuilder buf) {
936             int year = cal.get(Calendar.YEAR);
937
938             String s;
939             if (year <= 0) // negative value
940             {
941                 s = Integer.toString(1 - year);
942             } else // positive value
943             {
944                 s = Integer.toString(year);
945             }
946
947             while (s.length() < 4) {
948                 s = '0' + s;
949             }
950             if (year <= 0) {
951                 s = '-' + s;
952             }
953
954             buf.append(s);
955         }
956
957         private static void formatMonth(Calendar cal, StringBuilder buf) {
958             formatTwoDigits(cal.get(Calendar.MONTH) + 1, buf);
959         }
960
961         private static void formatDays(Calendar cal, StringBuilder buf) {
962             formatTwoDigits(cal.get(Calendar.DAY_OF_MONTH), buf);
963         }
964
965         private static void formatHours(Calendar cal, StringBuilder buf) {
966             formatTwoDigits(cal.get(Calendar.HOUR_OF_DAY), buf);
967         }
968
969         private static void formatMinutes(Calendar cal, StringBuilder buf) {
970             formatTwoDigits(cal.get(Calendar.MINUTE), buf);
971         }
972
973         private static void formatSeconds(Calendar cal, StringBuilder buf) {
974             formatTwoDigits(cal.get(Calendar.SECOND), buf);
975             if (cal.isSet(Calendar.MILLISECOND)) { // milliseconds
976                 int n = cal.get(Calendar.MILLISECOND);
977                 if (n != 0) {
978                     String ms = Integer.toString(n);
979                     while (ms.length() < 3) {
980                         ms = '0' + ms; // left 0 paddings.
981                     }
982                     buf.append('.');
983                     buf.append(ms);
984                 }
985             }
986         }
987
988         /** formats time zone specifier. */
989         private static void formatTimeZone(Calendar cal, StringBuilder buf) {
990             TimeZone tz = cal.getTimeZone();
991
992             if (tz == null) {
993                 return;
994             }
995
996             // otherwise print out normally.
997             int offset = tz.getOffset(cal.getTime().getTime());
998
999             if (offset == 0) {
1000                 buf.append('Z');
1001                 return;
1002             }
1003
1004             if (offset >= 0) {
1005                 buf.append('+');
1006             } else {
1007                 buf.append('-');
1008                 offset *= -1;
1009             }
1010
1011             offset /= 60 * 1000; // offset is in milli-seconds
1012
1013             formatTwoDigits(offset / 60, buf);
1014             buf.append(':');
1015             formatTwoDigits(offset % 60, buf);
1016         }
1017
1018         /** formats Integer into two-character-wide string. */
1019         private static void formatTwoDigits(int n, StringBuilder buf) {
1020             // n is always non-negative.
1021             if (n < 10) {
1022                 buf.append('0');
1023             }
1024             buf.append(n);
1025         }
1026     }
1027 }
1028