1 /*
2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3  *
4  * Copyright (c) 2007-2017 Oracle and/or its affiliates. All rights reserved.
5  *
6  * The contents of this file are subject to the terms of either the GNU
7  * General Public License Version 2 only ("GPL") or the Common Development
8  * and Distribution License("CDDL") (collectively, the "License").  You
9  * may not use this file except in compliance with the License.  You can
10  * obtain a copy of the License at
11  * https://oss.oracle.com/licenses/CDDL+GPL-1.1
12  * or LICENSE.txt.  See the License for the specific
13  * language governing permissions and limitations under the License.
14  *
15  * When distributing the software, include this License Header Notice in each
16  * file and include the License file at LICENSE.txt.
17  *
18  * GPL Classpath Exception:
19  * Oracle designates this particular file as subject to the "Classpath"
20  * exception as provided by Oracle in the GPL Version 2 section of the License
21  * file that accompanied this code.
22  *
23  * Modifications:
24  * If applicable, add the following below the License Header, with the fields
25  * enclosed by brackets [] replaced by your own identifying information:
26  * "Portions Copyright [year] [name of copyright owner]"
27  *
28  * Contributor(s):
29  * If you wish your version of this file to be governed by only the CDDL or
30  * only the GPL Version 2, indicate your decision by adding "[Contributor]
31  * elects to include this software in this distribution under the [CDDL or GPL
32  * Version 2] license."  If you don't indicate a single choice of license, a
33  * recipient has the option to distribute your version of this file under
34  * either the CDDL, the GPL Version 2 or to extend the choice of license to
35  * its licensees as provided above.  However, if you add GPL Version 2 code
36  * and therefore, elected the GPL Version 2 license, then the option applies
37  * only if the new code is made subject to such option by the copyright
38  * holder.
39  */

40
41 package javax.xml.bind;
42
43 import java.math.BigDecimal;
44 import java.math.BigInteger;
45 import java.util.Calendar;
46 import java.util.GregorianCalendar;
47 import java.util.TimeZone;
48
49 import javax.xml.namespace.QName;
50 import javax.xml.namespace.NamespaceContext;
51 import javax.xml.datatype.DatatypeFactory;
52 import javax.xml.datatype.DatatypeConfigurationException;
53
54 /**
55  * This class is the JAXB RI's default implementation of the
56  * {@link DatatypeConverterInterface}.
57  *
58  * <p>
59  * When client applications specify the use of the static print/parse
60  * methods in {@link DatatypeConverter}, it will delegate
61  * to this class.
62  *
63  * <p>
64  * This class is responsible for whitespace normalization.
65  *
66  * @author <ul><li>Ryan Shoemaker, Sun Microsystems, Inc.</li></ul>
67  * @since JAXB 2.1
68  */

69 final class DatatypeConverterImpl implements DatatypeConverterInterface {
70
71     /**
72      * To avoid re-creating instances, we cache one instance.
73      */

74     public static final DatatypeConverterInterface theInstance = new DatatypeConverterImpl();
75
76     protected DatatypeConverterImpl() {
77     }
78
79     public String parseString(String lexicalXSDString) {
80         return lexicalXSDString;
81     }
82
83     public BigInteger parseInteger(String lexicalXSDInteger) {
84         return _parseInteger(lexicalXSDInteger);
85     }
86
87     public static BigInteger _parseInteger(CharSequence s) {
88         return new BigInteger(removeOptionalPlus(WhiteSpaceProcessor.trim(s)).toString());
89     }
90
91     public String printInteger(BigInteger val) {
92         return _printInteger(val);
93     }
94
95     public static String _printInteger(BigInteger val) {
96         return val.toString();
97     }
98
99     public int parseInt(String s) {
100         return _parseInt(s);
101     }
102
103     /**
104      * Faster but less robust String->int conversion.
105      *
106      * Note that:
107      * <ol>
108      *  <li>XML Schema allows '+', but {@link Integer#valueOf(String)} is not.
109      *  <li>XML Schema allows leading and trailing (but not in-between) whitespaces.
110      *      {@link Integer#valueOf(String)} doesn't allow any.
111      * </ol>
112      */

113     public static int _parseInt(CharSequence s) {
114         int len = s.length();
115         int sign = 1;
116
117         int r = 0;
118
119         for (int i = 0; i < len; i++) {
120             char ch = s.charAt(i);
121             if (WhiteSpaceProcessor.isWhiteSpace(ch)) {
122                 // skip whitespace
123             } else if ('0' <= ch && ch <= '9') {
124                 r = r * 10 + (ch - '0');
125             } else if (ch == '-') {
126                 sign = -1;
127             } else if (ch == '+') {
128                 // noop
129             } else {
130                 throw new NumberFormatException("Not a number: " + s);
131             }
132         }
133
134         return r * sign;
135     }
136
137     public long parseLong(String lexicalXSLong) {
138         return _parseLong(lexicalXSLong);
139     }
140
141     public static long _parseLong(CharSequence s) {
142         return Long.parseLong(removeOptionalPlus(WhiteSpaceProcessor.trim(s)).toString());
143     }
144
145     public short parseShort(String lexicalXSDShort) {
146         return _parseShort(lexicalXSDShort);
147     }
148
149     public static short _parseShort(CharSequence s) {
150         return (short) _parseInt(s);
151     }
152
153     public String printShort(short val) {
154         return _printShort(val);
155     }
156
157     public static String _printShort(short val) {
158         return String.valueOf(val);
159     }
160
161     public BigDecimal parseDecimal(String content) {
162         return _parseDecimal(content);
163     }
164
165     public static BigDecimal _parseDecimal(CharSequence content) {
166         content = WhiteSpaceProcessor.trim(content);
167
168         if (content.length() <= 0) {
169             return null;
170         }
171
172         return new BigDecimal(content.toString());
173
174         // from purely XML Schema perspective,
175         // this implementation has a problem, since
176         // in xs:decimal "1.0" and "1" is equal whereas the above
177         // code will return different values for those two forms.
178         //
179         // the code was originally using com.sun.msv.datatype.xsd.NumberType.load,
180         // but a profiling showed that the process of normalizing "1.0" into "1"
181         // could take non-trivial time.
182         //
183         // also, from the user's point of view, one might be surprised if
184         // 1 (not 1.0) is returned from "1.000"
185     }
186
187     public float parseFloat(String lexicalXSDFloat) {
188         return _parseFloat(lexicalXSDFloat);
189     }
190
191     public static float _parseFloat(CharSequence _val) {
192         String s = WhiteSpaceProcessor.trim(_val).toString();
193         /* Incompatibilities of XML Schema's float "xfloat" and Java's float "jfloat"
194
195          * jfloat.valueOf ignores leading and trailing whitespaces,
196         whereas this is not allowed in xfloat.
197          * jfloat.valueOf allows "float type suffix" (f, F) to be
198         appended after float literal (e.g., 1.52e-2f), whereare
199         this is not the case of xfloat.
200
201         gray zone
202         ---------
203          * jfloat allows ".523". And there is no clear statement that mentions
204         this case in xfloat. Although probably this is allowed.
205          *
206          */

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

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

617     public static String installHook(String s) {
618         DatatypeConverter.setDatatypeConverter(theInstance);
619         return s;
620     }
621 // base64 decoder
622     private static final byte[] decodeMap = initDecodeMap();
623     private static final byte PADDING = 127;
624
625     private static byte[] initDecodeMap() {
626         byte[] map = new byte[128];
627         int i;
628         for (i = 0; i < 128; i++) {
629             map[i] = -1;
630         }
631
632         for (i = 'A'; i <= 'Z'; i++) {
633             map[i] = (byte) (i - 'A');
634         }
635         for (i = 'a'; i <= 'z'; i++) {
636             map[i] = (byte) (i - 'a' + 26);
637         }
638         for (i = '0'; i <= '9'; i++) {
639             map[i] = (byte) (i - '0' + 52);
640         }
641         map['+'] = 62;
642         map['/'] = 63;
643         map['='] = PADDING;
644
645         return map;
646     }
647
648     /**
649      * computes the length of binary data speculatively.
650      *
651      * <p>
652      * Our requirement is to create byte[] of the exact length to store the binary data.
653      * If we do this in a straight-forward way, it takes two passes over the data.
654      * Experiments show that this is a non-trivial overhead (35% or so is spent on
655      * the first pass in calculating the length.)
656      *
657      * <p>
658      * So the approach here is that we compute the length speculatively, without looking
659      * at the whole contents. The obtained speculative value is never less than the
660      * actual length of the binary data, but it may be bigger. So if the speculation
661      * goes wrong, we'll pay the cost of reallocation and buffer copying.
662      *
663      * <p>
664      * If the base64 text is tightly packed with no indentation nor illegal char
665      * (like what most web services produce), then the speculation of this method
666      * will be correct, so we get the performance benefit.
667      */

668     private static int guessLength(String text) {
669         final int len = text.length();
670
671         // compute the tail '=' chars
672         int j = len - 1;
673         for (; j >= 0; j--) {
674             byte code = decodeMap[text.charAt(j)];
675             if (code == PADDING) {
676                 continue;
677             }
678             if (code == -1) // most likely this base64 text is indented. go with the upper bound
679             {
680                 return text.length() / 4 * 3;
681             }
682             break;
683         }
684
685         j++;    // text.charAt(j) is now at some base64 char, so +1 to make it the size
686         int padSize = len - j;
687         if (padSize > 2) // something is wrong with base64. be safe and go with the upper bound
688         {
689             return text.length() / 4 * 3;
690         }
691
692         // so far this base64 looks like it's unindented tightly packed base64.
693         // take a chance and create an array with the expected size
694         return text.length() / 4 * 3 - padSize;
695     }
696
697     /**
698      * @param text
699      *      base64Binary data is likely to be long, and decoding requires
700      *      each character to be accessed twice (once for counting length, another
701      *      for decoding.)
702      *
703      *      A benchmark showed that taking {@link String} is faster, presumably
704      *      because JIT can inline a lot of string access (with data of 1K chars, it was twice as fast)
705      */

706     public static byte[] _parseBase64Binary(String text) {
707         final int buflen = guessLength(text);
708         final byte[] out = new byte[buflen];
709         int o = 0;
710
711         final int len = text.length();
712         int i;
713
714         final byte[] quadruplet = new byte[4];
715         int q = 0;
716
717         // convert each quadruplet to three bytes.
718         for (i = 0; i < len; i++) {
719             char ch = text.charAt(i);
720             byte v = decodeMap[ch];
721
722             if (v != -1) {
723                 quadruplet[q++] = v;
724             }
725
726             if (q == 4) {
727                 // quadruplet is now filled.
728                 out[o++] = (byte) ((quadruplet[0] << 2) | (quadruplet[1] >> 4));
729                 if (quadruplet[2] != PADDING) {
730                     out[o++] = (byte) ((quadruplet[1] << 4) | (quadruplet[2] >> 2));
731                 }
732                 if (quadruplet[3] != PADDING) {
733                     out[o++] = (byte) ((quadruplet[2] << 6) | (quadruplet[3]));
734                 }
735                 q = 0;
736             }
737         }
738
739         if (buflen == o) // speculation worked out to be OK
740         {
741             return out;
742         }
743
744         // we overestimated, so need to create a new buffer
745         byte[] nb = new byte[o];
746         System.arraycopy(out, 0, nb, 0, o);
747         return nb;
748     }
749     private static final char[] encodeMap = initEncodeMap();
750
751     private static char[] initEncodeMap() {
752         char[] map = new char[64];
753         int i;
754         for (i = 0; i < 26; i++) {
755             map[i] = (char) ('A' + i);
756         }
757         for (i = 26; i < 52; i++) {
758             map[i] = (char) ('a' + (i - 26));
759         }
760         for (i = 52; i < 62; i++) {
761             map[i] = (char) ('0' + (i - 52));
762         }
763         map[62] = '+';
764         map[63] = '/';
765
766         return map;
767     }
768
769     public static char encode(int i) {
770         return encodeMap[i & 0x3F];
771     }
772
773     public static byte encodeByte(int i) {
774         return (byte) encodeMap[i & 0x3F];
775     }
776
777     public static String _printBase64Binary(byte[] input) {
778         return _printBase64Binary(input, 0, input.length);
779     }
780
781     public static String _printBase64Binary(byte[] input, int offset, int len) {
782         char[] buf = new char[((len + 2) / 3) * 4];
783         int ptr = _printBase64Binary(input, offset, len, buf, 0);
784         assert ptr == buf.length;
785         return new String(buf);
786     }
787
788     /**
789      * Encodes a byte array into a char array by doing base64 encoding.
790      *
791      * The caller must supply a big enough buffer.
792      *
793      * @return
794      *      the value of {@code ptr+((len+2)/3)*4}, which is the new offset
795      *      in the output buffer where the further bytes should be placed.
796      */

797     public static int _printBase64Binary(byte[] input, int offset, int len, char[] buf, int ptr) {
798         // encode elements until only 1 or 2 elements are left to encode
799         int remaining = len;
800         int i;
801         for (i = offset;remaining >= 3; remaining -= 3, i += 3) {
802             buf[ptr++] = encode(input[i] >> 2);
803             buf[ptr++] = encode(
804                     ((input[i] & 0x3) << 4)
805                     | ((input[i + 1] >> 4) & 0xF));
806             buf[ptr++] = encode(
807                     ((input[i + 1] & 0xF) << 2)
808                     | ((input[i + 2] >> 6) & 0x3));
809             buf[ptr++] = encode(input[i + 2] & 0x3F);
810         }
811         // encode when exactly 1 element (left) to encode
812         if (remaining == 1) {
813             buf[ptr++] = encode(input[i] >> 2);
814             buf[ptr++] = encode(((input[i]) & 0x3) << 4);
815             buf[ptr++] = '=';
816             buf[ptr++] = '=';
817         }
818         // encode when exactly 2 elements (left) to encode
819         if (remaining == 2) {
820             buf[ptr++] = encode(input[i] >> 2);
821             buf[ptr++] = encode(((input[i] & 0x3) << 4)
822                     | ((input[i + 1] >> 4) & 0xF));
823             buf[ptr++] = encode((input[i + 1] & 0xF) << 2);
824             buf[ptr++] = '=';
825         }
826         return ptr;
827     }
828
829     /**
830      * Encodes a byte array into another byte array by first doing base64 encoding
831      * then encoding the result in ASCII.
832      *
833      * The caller must supply a big enough buffer.
834      *
835      * @return
836      *      the value of {@code ptr+((len+2)/3)*4}, which is the new offset
837      *      in the output buffer where the further bytes should be placed.
838      */

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