1/*
2 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package javax.xml.bind;
27
28import java.math.BigDecimal;
29import java.math.BigInteger;
30import java.util.Calendar;
31import java.util.GregorianCalendar;
32import java.util.TimeZone;
33
34import javax.xml.namespace.QName;
35import javax.xml.namespace.NamespaceContext;
36import javax.xml.datatype.DatatypeFactory;
37import javax.xml.datatype.DatatypeConfigurationException;
38
39/**
40 * This class is the JAXB RI's default implementation of the
41 * {@link DatatypeConverterInterface}.
42 *
43 * <p>
44 * When client applications specify the use of the static print/parse
45 * methods in {@link DatatypeConverter}, it will delegate
46 * to this class.
47 *
48 * <p>
49 * This class is responsible for whitespace normalization.
50 *
51 * @author <ul><li>Ryan Shoemaker, Sun Microsystems, Inc.</li></ul>
52 * @since JAXB 2.1
53 */
54final class DatatypeConverterImpl implements DatatypeConverterInterface {
55
56    /**
57     * To avoid re-creating instances, we cache one instance.
58     */
59    public static final DatatypeConverterInterface theInstance = new DatatypeConverterImpl();
60
61    protected DatatypeConverterImpl() {
62    }
63
64    public String parseString(String lexicalXSDString) {
65        return lexicalXSDString;
66    }
67
68    public BigInteger parseInteger(String lexicalXSDInteger) {
69        return _parseInteger(lexicalXSDInteger);
70    }
71
72    public static BigInteger _parseInteger(CharSequence s) {
73        return new BigInteger(removeOptionalPlus(WhiteSpaceProcessor.trim(s)).toString());
74    }
75
76    public String printInteger(BigInteger val) {
77        return _printInteger(val);
78    }
79
80    public static String _printInteger(BigInteger val) {
81        return val.toString();
82    }
83
84    public int parseInt(String s) {
85        return _parseInt(s);
86    }
87
88    /**
89     * Faster but less robust String->int conversion.
90     *
91     * Note that:
92     * <ol>
93     *  <li>XML Schema allows '+', but {@link Integer#valueOf(String)} is not.
94     *  <li>XML Schema allows leading and trailing (but not in-between) whitespaces.
95     *      {@link Integer#valueOf(String)} doesn't allow any.
96     * </ol>
97     */
98    public static int _parseInt(CharSequence s) {
99        int len = s.length();
100        int sign = 1;
101
102        int r = 0;
103
104        for (int i = 0; i < len; i++) {
105            char ch = s.charAt(i);
106            if (WhiteSpaceProcessor.isWhiteSpace(ch)) {
107                // skip whitespace
108            } else if ('0' <= ch && ch <= '9') {
109                r = r * 10 + (ch - '0');
110            } else if (ch == '-') {
111                sign = -1;
112            } else if (ch == '+') {
113                // noop
114            } else {
115                throw new NumberFormatException("Not a number: " + s);
116            }
117        }
118
119        return r * sign;
120    }
121
122    public long parseLong(String lexicalXSLong) {
123        return _parseLong(lexicalXSLong);
124    }
125
126    public static long _parseLong(CharSequence s) {
127        return Long.parseLong(removeOptionalPlus(WhiteSpaceProcessor.trim(s)).toString());
128    }
129
130    public short parseShort(String lexicalXSDShort) {
131        return _parseShort(lexicalXSDShort);
132    }
133
134    public static short _parseShort(CharSequence s) {
135        return (short) _parseInt(s);
136    }
137
138    public String printShort(short val) {
139        return _printShort(val);
140    }
141
142    public static String _printShort(short val) {
143        return String.valueOf(val);
144    }
145
146    public BigDecimal parseDecimal(String content) {
147        return _parseDecimal(content);
148    }
149
150    public static BigDecimal _parseDecimal(CharSequence content) {
151        content = WhiteSpaceProcessor.trim(content);
152
153        if (content.length() <= 0) {
154            return null;
155        }
156
157        return new BigDecimal(content.toString());
158
159        // from purely XML Schema perspective,
160        // this implementation has a problem, since
161        // in xs:decimal "1.0" and "1" is equal whereas the above
162        // code will return different values for those two forms.
163        //
164        // the code was originally using com.sun.msv.datatype.xsd.NumberType.load,
165        // but a profiling showed that the process of normalizing "1.0" into "1"
166        // could take non-trivial time.
167        //
168        // also, from the user's point of view, one might be surprised if
169        // 1 (not 1.0) is returned from "1.000"
170    }
171
172    public float parseFloat(String lexicalXSDFloat) {
173        return _parseFloat(lexicalXSDFloat);
174    }
175
176    public static float _parseFloat(CharSequence _val) {
177        String s = WhiteSpaceProcessor.trim(_val).toString();
178        /* Incompatibilities of XML Schema's float "xfloat" and Java's float "jfloat"
179
180         * jfloat.valueOf ignores leading and trailing whitespaces,
181        whereas this is not allowed in xfloat.
182         * jfloat.valueOf allows "float type suffix" (f, F) to be
183        appended after float literal (e.g., 1.52e-2f), whereare
184        this is not the case of xfloat.
185
186        gray zone
187        ---------
188         * jfloat allows ".523". And there is no clear statement that mentions
189        this case in xfloat. Although probably this is allowed.
190         *
191         */
192
193        if (s.equals("NaN")) {
194            return Float.NaN;
195        }
196        if (s.equals("INF")) {
197            return Float.POSITIVE_INFINITY;
198        }
199        if (s.equals("-INF")) {
200            return Float.NEGATIVE_INFINITY;
201        }
202
203        if (s.length() == 0
204                || !isDigitOrPeriodOrSign(s.charAt(0))
205                || !isDigitOrPeriodOrSign(s.charAt(s.length() - 1))) {
206            throw new NumberFormatException();
207        }
208
209        // these screening process is necessary due to the wobble of Float.valueOf method
210        return Float.parseFloat(s);
211    }
212
213    public String printFloat(float v) {
214        return _printFloat(v);
215    }
216
217    public static String _printFloat(float v) {
218        if (Float.isNaN(v)) {
219            return "NaN";
220        }
221        if (v == Float.POSITIVE_INFINITY) {
222            return "INF";
223        }
224        if (v == Float.NEGATIVE_INFINITY) {
225            return "-INF";
226        }
227        return String.valueOf(v);
228    }
229
230    public double parseDouble(String lexicalXSDDouble) {
231        return _parseDouble(lexicalXSDDouble);
232    }
233
234    public static double _parseDouble(CharSequence _val) {
235        String val = WhiteSpaceProcessor.trim(_val).toString();
236
237        if (val.equals("NaN")) {
238            return Double.NaN;
239        }
240        if (val.equals("INF")) {
241            return Double.POSITIVE_INFINITY;
242        }
243        if (val.equals("-INF")) {
244            return Double.NEGATIVE_INFINITY;
245        }
246
247        if (val.length() == 0
248                || !isDigitOrPeriodOrSign(val.charAt(0))
249                || !isDigitOrPeriodOrSign(val.charAt(val.length() - 1))) {
250            throw new NumberFormatException(val);
251        }
252
253
254        // these screening process is necessary due to the wobble of Float.valueOf method
255        return Double.parseDouble(val);
256    }
257
258    public boolean parseBoolean(String lexicalXSDBoolean) {
259        Boolean b = _parseBoolean(lexicalXSDBoolean);
260        return (b == null) ? false : b.booleanValue();
261    }
262
263    public static Boolean _parseBoolean(CharSequence literal) {
264        if (literal == null) {
265            return null;
266        }
267
268        int i = 0;
269        int len = literal.length();
270        char ch;
271        boolean value = false;
272
273        if (literal.length() <= 0) {
274            return null;
275        }
276
277        do {
278            ch = literal.charAt(i++);
279        } while (WhiteSpaceProcessor.isWhiteSpace(ch) && i < len);
280
281        int strIndex = 0;
282
283        switch (ch) {
284            case '1':
285                value = true;
286                break;
287            case '0':
288                value = false;
289                break;
290            case 't':
291                String strTrue = "rue";
292                do {
293                    ch = literal.charAt(i++);
294                } while ((strTrue.charAt(strIndex++) == ch) && i < len && strIndex < 3);
295
296                if (strIndex == 3) {
297                    value = true;
298                } else {
299                    return false;
300                }
301//                    throw new IllegalArgumentException("String \"" + literal + "\" is not valid boolean value.");
302
303                break;
304            case 'f':
305                String strFalse = "alse";
306                do {
307                    ch = literal.charAt(i++);
308                } while ((strFalse.charAt(strIndex++) == ch) && i < len && strIndex < 4);
309
310
311                if (strIndex == 4) {
312                    value = false;
313                } else {
314                    return false;
315                }
316//                    throw new IllegalArgumentException("String \"" + literal + "\" is not valid boolean value.");
317
318                break;
319        }
320
321        if (i < len) {
322            do {
323                ch = literal.charAt(i++);
324            } while (WhiteSpaceProcessor.isWhiteSpace(ch) && i < len);
325        }
326
327        if (i == len) {
328            return value;
329        } else {
330            return null;
331        }
332//            throw new IllegalArgumentException("String \"" + literal + "\" is not valid boolean value.");
333    }
334
335    public String printBoolean(boolean val) {
336        return val ? "true" : "false";
337    }
338
339    public static String _printBoolean(boolean val) {
340        return val ? "true" : "false";
341    }
342
343    public byte parseByte(String lexicalXSDByte) {
344        return _parseByte(lexicalXSDByte);
345    }
346
347    public static byte _parseByte(CharSequence literal) {
348        return (byte) _parseInt(literal);
349    }
350
351    public String printByte(byte val) {
352        return _printByte(val);
353    }
354
355    public static String _printByte(byte val) {
356        return String.valueOf(val);
357    }
358
359    public QName parseQName(String lexicalXSDQName, NamespaceContext nsc) {
360        return _parseQName(lexicalXSDQName, nsc);
361    }
362
363    /**
364     * @return null if fails to convert.
365     */
366    public static QName _parseQName(CharSequence text, NamespaceContext nsc) {
367        int length = text.length();
368
369        // trim whitespace
370        int start = 0;
371        while (start < length && WhiteSpaceProcessor.isWhiteSpace(text.charAt(start))) {
372            start++;
373        }
374
375        int end = length;
376        while (end > start && WhiteSpaceProcessor.isWhiteSpace(text.charAt(end - 1))) {
377            end--;
378        }
379
380        if (end == start) {
381            throw new IllegalArgumentException("input is empty");
382        }
383
384
385        String uri;
386        String localPart;
387        String prefix;
388
389        // search ':'
390        int idx = start + 1;    // no point in searching the first char. that's not valid.
391        while (idx < end && text.charAt(idx) != ':') {
392            idx++;
393        }
394
395        if (idx == end) {
396            uri = nsc.getNamespaceURI("");
397            localPart = text.subSequence(start, end).toString();
398            prefix = "";
399        } else {
400            // Prefix exists, check everything
401            prefix = text.subSequence(start, idx).toString();
402            localPart = text.subSequence(idx + 1, end).toString();
403            uri = nsc.getNamespaceURI(prefix);
404            // uri can never be null according to javadoc,
405            // but some users reported that there are implementations that return null.
406            if (uri == null || uri.length() == 0) // crap. the NamespaceContext interface is broken.
407            // error: unbound prefix
408            {
409                throw new IllegalArgumentException("prefix " + prefix + " is not bound to a namespace");
410            }
411        }
412
413        return new QName(uri, localPart, prefix);
414    }
415
416    public Calendar parseDateTime(String lexicalXSDDateTime) {
417        return _parseDateTime(lexicalXSDDateTime);
418    }
419
420    public static GregorianCalendar _parseDateTime(CharSequence s) {
421        String val = WhiteSpaceProcessor.trim(s).toString();
422        return datatypeFactory.newXMLGregorianCalendar(val).toGregorianCalendar();
423    }
424
425    public String printDateTime(Calendar val) {
426        return _printDateTime(val);
427    }
428
429    public static String _printDateTime(Calendar val) {
430        return CalendarFormatter.doFormat("%Y-%M-%DT%h:%m:%s%z", val);
431    }
432
433    public byte[] parseBase64Binary(String lexicalXSDBase64Binary) {
434        return _parseBase64Binary(lexicalXSDBase64Binary);
435    }
436
437    public byte[] parseHexBinary(String s) {
438        final int len = s.length();
439
440        // "111" is not a valid hex encoding.
441        if (len % 2 != 0) {
442            throw new IllegalArgumentException("hexBinary needs to be even-length: " + s);
443        }
444
445        byte[] out = new byte[len / 2];
446
447        for (int i = 0; i < len; i += 2) {
448            int h = hexToBin(s.charAt(i));
449            int l = hexToBin(s.charAt(i + 1));
450            if (h == -1 || l == -1) {
451                throw new IllegalArgumentException("contains illegal character for hexBinary: " + s);
452            }
453
454            out[i / 2] = (byte) (h * 16 + l);
455        }
456
457        return out;
458    }
459
460    private static int hexToBin(char ch) {
461        if ('0' <= ch && ch <= '9') {
462            return ch - '0';
463        }
464        if ('A' <= ch && ch <= 'F') {
465            return ch - 'A' + 10;
466        }
467        if ('a' <= ch && ch <= 'f') {
468            return ch - 'a' + 10;
469        }
470        return -1;
471    }
472    private static final char[] hexCode = "0123456789ABCDEF".toCharArray();
473
474    public String printHexBinary(byte[] data) {
475        StringBuilder r = new StringBuilder(data.length * 2);
476        for (byte b : data) {
477            r.append(hexCode[(b >> 4) & 0xF]);
478            r.append(hexCode[(b & 0xF)]);
479        }
480        return r.toString();
481    }
482
483    public long parseUnsignedInt(String lexicalXSDUnsignedInt) {
484        return _parseLong(lexicalXSDUnsignedInt);
485    }
486
487    public String printUnsignedInt(long val) {
488        return _printLong(val);
489    }
490
491    public int parseUnsignedShort(String lexicalXSDUnsignedShort) {
492        return _parseInt(lexicalXSDUnsignedShort);
493    }
494
495    public Calendar parseTime(String lexicalXSDTime) {
496        return datatypeFactory.newXMLGregorianCalendar(lexicalXSDTime).toGregorianCalendar();
497    }
498
499    public String printTime(Calendar val) {
500        return CalendarFormatter.doFormat("%h:%m:%s%z", val);
501    }
502
503    public Calendar parseDate(String lexicalXSDDate) {
504        return datatypeFactory.newXMLGregorianCalendar(lexicalXSDDate).toGregorianCalendar();
505    }
506
507    public String printDate(Calendar val) {
508        return _printDate(val);
509    }
510
511    public static String _printDate(Calendar val) {
512        return CalendarFormatter.doFormat((new StringBuilder("%Y-%M-%D").append("%z")).toString(),val);
513    }
514
515    public String parseAnySimpleType(String lexicalXSDAnySimpleType) {
516        return lexicalXSDAnySimpleType;
517//        return (String)SimpleURType.theInstance._createValue( lexicalXSDAnySimpleType, null );
518    }
519
520    public String printString(String val) {
521//        return StringType.theInstance.convertToLexicalValue( val, null );
522        return val;
523    }
524
525    public String printInt(int val) {
526        return _printInt(val);
527    }
528
529    public static String _printInt(int val) {
530        return String.valueOf(val);
531    }
532
533    public String printLong(long val) {
534        return _printLong(val);
535    }
536
537    public static String _printLong(long val) {
538        return String.valueOf(val);
539    }
540
541    public String printDecimal(BigDecimal val) {
542        return _printDecimal(val);
543    }
544
545    public static String _printDecimal(BigDecimal val) {
546        return val.toPlainString();
547    }
548
549    public String printDouble(double v) {
550        return _printDouble(v);
551    }
552
553    public static String _printDouble(double v) {
554        if (Double.isNaN(v)) {
555            return "NaN";
556        }
557        if (v == Double.POSITIVE_INFINITY) {
558            return "INF";
559        }
560        if (v == Double.NEGATIVE_INFINITY) {
561            return "-INF";
562        }
563        return String.valueOf(v);
564    }
565
566    public String printQName(QName val, NamespaceContext nsc) {
567        return _printQName(val, nsc);
568    }
569
570    public static String _printQName(QName val, NamespaceContext nsc) {
571        // Double-check
572        String qname;
573        String prefix = nsc.getPrefix(val.getNamespaceURI());
574        String localPart = val.getLocalPart();
575
576        if (prefix == null || prefix.length() == 0) { // be defensive
577            qname = localPart;
578        } else {
579            qname = prefix + ':' + localPart;
580        }
581
582        return qname;
583    }
584
585    public String printBase64Binary(byte[] val) {
586        return _printBase64Binary(val);
587    }
588
589    public String printUnsignedShort(int val) {
590        return String.valueOf(val);
591    }
592
593    public String printAnySimpleType(String val) {
594        return val;
595    }
596
597    /**
598     * Just return the string passed as a parameter but
599     * installs an instance of this class as the DatatypeConverter
600     * implementation. Used from static fixed value initializers.
601     */
602    public static String installHook(String s) {
603        DatatypeConverter.setDatatypeConverter(theInstance);
604        return s;
605    }
606// base64 decoder
607    private static final byte[] decodeMap = initDecodeMap();
608    private static final byte PADDING = 127;
609
610    private static byte[] initDecodeMap() {
611        byte[] map = new byte[128];
612        int i;
613        for (i = 0; i < 128; i++) {
614            map[i] = -1;
615        }
616
617        for (i = 'A'; i <= 'Z'; i++) {
618            map[i] = (byte) (i - 'A');
619        }
620        for (i = 'a'; i <= 'z'; i++) {
621            map[i] = (byte) (i - 'a' + 26);
622        }
623        for (i = '0'; i <= '9'; i++) {
624            map[i] = (byte) (i - '0' + 52);
625        }
626        map['+'] = 62;
627        map['/'] = 63;
628        map['='] = PADDING;
629
630        return map;
631    }
632
633    /**
634     * computes the length of binary data speculatively.
635     *
636     * <p>
637     * Our requirement is to create byte[] of the exact length to store the binary data.
638     * If we do this in a straight-forward way, it takes two passes over the data.
639     * Experiments show that this is a non-trivial overhead (35% or so is spent on
640     * the first pass in calculating the length.)
641     *
642     * <p>
643     * So the approach here is that we compute the length speculatively, without looking
644     * at the whole contents. The obtained speculative value is never less than the
645     * actual length of the binary data, but it may be bigger. So if the speculation
646     * goes wrong, we'll pay the cost of reallocation and buffer copying.
647     *
648     * <p>
649     * If the base64 text is tightly packed with no indentation nor illegal char
650     * (like what most web services produce), then the speculation of this method
651     * will be correct, so we get the performance benefit.
652     */
653    private static int guessLength(String text) {
654        final int len = text.length();
655
656        // compute the tail '=' chars
657        int j = len - 1;
658        for (; j >= 0; j--) {
659            byte code = decodeMap[text.charAt(j)];
660            if (code == PADDING) {
661                continue;
662            }
663            if (code == -1) // most likely this base64 text is indented. go with the upper bound
664            {
665                return text.length() / 4 * 3;
666            }
667            break;
668        }
669
670        j++;    // text.charAt(j) is now at some base64 char, so +1 to make it the size
671        int padSize = len - j;
672        if (padSize > 2) // something is wrong with base64. be safe and go with the upper bound
673        {
674            return text.length() / 4 * 3;
675        }
676
677        // so far this base64 looks like it's unindented tightly packed base64.
678        // take a chance and create an array with the expected size
679        return text.length() / 4 * 3 - padSize;
680    }
681
682    /**
683     * @param text
684     *      base64Binary data is likely to be long, and decoding requires
685     *      each character to be accessed twice (once for counting length, another
686     *      for decoding.)
687     *
688     *      A benchmark showed that taking {@link String} is faster, presumably
689     *      because JIT can inline a lot of string access (with data of 1K chars, it was twice as fast)
690     */
691    public static byte[] _parseBase64Binary(String text) {
692        final int buflen = guessLength(text);
693        final byte[] out = new byte[buflen];
694        int o = 0;
695
696        final int len = text.length();
697        int i;
698
699        final byte[] quadruplet = new byte[4];
700        int q = 0;
701
702        // convert each quadruplet to three bytes.
703        for (i = 0; i < len; i++) {
704            char ch = text.charAt(i);
705            byte v = decodeMap[ch];
706
707            if (v != -1) {
708                quadruplet[q++] = v;
709            }
710
711            if (q == 4) {
712                // quadruplet is now filled.
713                out[o++] = (byte) ((quadruplet[0] << 2) | (quadruplet[1] >> 4));
714                if (quadruplet[2] != PADDING) {
715                    out[o++] = (byte) ((quadruplet[1] << 4) | (quadruplet[2] >> 2));
716                }
717                if (quadruplet[3] != PADDING) {
718                    out[o++] = (byte) ((quadruplet[2] << 6) | (quadruplet[3]));
719                }
720                q = 0;
721            }
722        }
723
724        if (buflen == o) // speculation worked out to be OK
725        {
726            return out;
727        }
728
729        // we overestimated, so need to create a new buffer
730        byte[] nb = new byte[o];
731        System.arraycopy(out, 0, nb, 0, o);
732        return nb;
733    }
734    private static final char[] encodeMap = initEncodeMap();
735
736    private static char[] initEncodeMap() {
737        char[] map = new char[64];
738        int i;
739        for (i = 0; i < 26; i++) {
740            map[i] = (char) ('A' + i);
741        }
742        for (i = 26; i < 52; i++) {
743            map[i] = (char) ('a' + (i - 26));
744        }
745        for (i = 52; i < 62; i++) {
746            map[i] = (char) ('0' + (i - 52));
747        }
748        map[62] = '+';
749        map[63] = '/';
750
751        return map;
752    }
753
754    public static char encode(int i) {
755        return encodeMap[i & 0x3F];
756    }
757
758    public static byte encodeByte(int i) {
759        return (byte) encodeMap[i & 0x3F];
760    }
761
762    public static String _printBase64Binary(byte[] input) {
763        return _printBase64Binary(input, 0, input.length);
764    }
765
766    public static String _printBase64Binary(byte[] input, int offset, int len) {
767        char[] buf = new char[((len + 2) / 3) * 4];
768        int ptr = _printBase64Binary(input, offset, len, buf, 0);
769        assert ptr == buf.length;
770        return new String(buf);
771    }
772
773    /**
774     * Encodes a byte array into a char array by doing base64 encoding.
775     *
776     * The caller must supply a big enough buffer.
777     *
778     * @return
779     *      the value of {@code ptr+((len+2)/3)*4}, which is the new offset
780     *      in the output buffer where the further bytes should be placed.
781     */
782    public static int _printBase64Binary(byte[] input, int offset, int len, char[] buf, int ptr) {
783        // encode elements until only 1 or 2 elements are left to encode
784        int remaining = len;
785        int i;
786        for (i = offset;remaining >= 3; remaining -= 3, i += 3) {
787            buf[ptr++] = encode(input[i] >> 2);
788            buf[ptr++] = encode(
789                    ((input[i] & 0x3) << 4)
790                    | ((input[i + 1] >> 4) & 0xF));
791            buf[ptr++] = encode(
792                    ((input[i + 1] & 0xF) << 2)
793                    | ((input[i + 2] >> 6) & 0x3));
794            buf[ptr++] = encode(input[i + 2] & 0x3F);
795        }
796        // encode when exactly 1 element (left) to encode
797        if (remaining == 1) {
798            buf[ptr++] = encode(input[i] >> 2);
799            buf[ptr++] = encode(((input[i]) & 0x3) << 4);
800            buf[ptr++] = '=';
801            buf[ptr++] = '=';
802        }
803        // encode when exactly 2 elements (left) to encode
804        if (remaining == 2) {
805            buf[ptr++] = encode(input[i] >> 2);
806            buf[ptr++] = encode(((input[i] & 0x3) << 4)
807                    | ((input[i + 1] >> 4) & 0xF));
808            buf[ptr++] = encode((input[i + 1] & 0xF) << 2);
809            buf[ptr++] = '=';
810        }
811        return ptr;
812    }
813
814    /**
815     * Encodes a byte array into another byte array by first doing base64 encoding
816     * then encoding the result in ASCII.
817     *
818     * The caller must supply a big enough buffer.
819     *
820     * @return
821     *      the value of {@code ptr+((len+2)/3)*4}, which is the new offset
822     *      in the output buffer where the further bytes should be placed.
823     */
824    public static int _printBase64Binary(byte[] input, int offset, int len, byte[] out, int ptr) {
825        byte[] buf = out;
826        int remaining = len;
827        int i;
828        for (i=offset; remaining >= 3; remaining -= 3, i += 3 ) {
829            buf[ptr++] = encodeByte(input[i]>>2);
830            buf[ptr++] = encodeByte(
831                        ((input[i]&0x3)<<4) |
832                        ((input[i+1]>>4)&0xF));
833            buf[ptr++] = encodeByte(
834                        ((input[i+1]&0xF)<<2)|
835                        ((input[i+2]>>6)&0x3));
836            buf[ptr++] = encodeByte(input[i+2]&0x3F);
837        }
838        // encode when exactly 1 element (left) to encode
839        if (remaining == 1) {
840            buf[ptr++] = encodeByte(input[i]>>2);
841            buf[ptr++] = encodeByte(((input[i])&0x3)<<4);
842            buf[ptr++] = '=';
843            buf[ptr++] = '=';
844        }
845        // encode when exactly 2 elements (left) to encode
846        if (remaining == 2) {
847            buf[ptr++] = encodeByte(input[i]>>2);
848            buf[ptr++] = encodeByte(
849                        ((input[i]&0x3)<<4) |
850                        ((input[i+1]>>4)&0xF));
851            buf[ptr++] = encodeByte((input[i+1]&0xF)<<2);
852            buf[ptr++] = '=';
853        }
854
855        return ptr;
856    }
857
858    private static CharSequence removeOptionalPlus(CharSequence s) {
859        int len = s.length();
860
861        if (len <= 1 || s.charAt(0) != '+') {
862            return s;
863        }
864
865        s = s.subSequence(1, len);
866        char ch = s.charAt(0);
867        if ('0' <= ch && ch <= '9') {
868            return s;
869        }
870        if ('.' == ch) {
871            return s;
872        }
873
874        throw new NumberFormatException();
875    }
876
877    private static boolean isDigitOrPeriodOrSign(char ch) {
878        if ('0' <= ch && ch <= '9') {
879            return true;
880        }
881        if (ch == '+' || ch == '-' || ch == '.') {
882            return true;
883        }
884        return false;
885    }
886    private static final DatatypeFactory datatypeFactory;
887
888    static {
889        try {
890            datatypeFactory = DatatypeFactory.newInstance();
891        } catch (DatatypeConfigurationException e) {
892            throw new Error(e);
893        }
894    }
895
896    private static final class CalendarFormatter {
897
898        public static String doFormat(String format, Calendar cal) throws IllegalArgumentException {
899            int fidx = 0;
900            int flen = format.length();
901            StringBuilder buf = new StringBuilder();
902
903            while (fidx < flen) {
904                char fch = format.charAt(fidx++);
905
906                if (fch != '%') {  // not a meta character
907                    buf.append(fch);
908                    continue;
909                }
910
911                // seen meta character. we don't do error check against the format
912                switch (format.charAt(fidx++)) {
913                    case 'Y': // year
914                        formatYear(cal, buf);
915                        break;
916
917                    case 'M': // month
918                        formatMonth(cal, buf);
919                        break;
920
921                    case 'D': // days
922                        formatDays(cal, buf);
923                        break;
924
925                    case 'h': // hours
926                        formatHours(cal, buf);
927                        break;
928
929                    case 'm': // minutes
930                        formatMinutes(cal, buf);
931                        break;
932
933                    case 's': // parse seconds.
934                        formatSeconds(cal, buf);
935                        break;
936
937                    case 'z': // time zone
938                        formatTimeZone(cal, buf);
939                        break;
940
941                    default:
942                        // illegal meta character. impossible.
943                        throw new InternalError();
944                }
945            }
946
947            return buf.toString();
948        }
949
950        private static void formatYear(Calendar cal, StringBuilder buf) {
951            int year = cal.get(Calendar.YEAR);
952
953            String s;
954            if (year <= 0) // negative value
955            {
956                s = Integer.toString(1 - year);
957            } else // positive value
958            {
959                s = Integer.toString(year);
960            }
961
962            while (s.length() < 4) {
963                s = '0' + s;
964            }
965            if (year <= 0) {
966                s = '-' + s;
967            }
968
969            buf.append(s);
970        }
971
972        private static void formatMonth(Calendar cal, StringBuilder buf) {
973            formatTwoDigits(cal.get(Calendar.MONTH) + 1, buf);
974        }
975
976        private static void formatDays(Calendar cal, StringBuilder buf) {
977            formatTwoDigits(cal.get(Calendar.DAY_OF_MONTH), buf);
978        }
979
980        private static void formatHours(Calendar cal, StringBuilder buf) {
981            formatTwoDigits(cal.get(Calendar.HOUR_OF_DAY), buf);
982        }
983
984        private static void formatMinutes(Calendar cal, StringBuilder buf) {
985            formatTwoDigits(cal.get(Calendar.MINUTE), buf);
986        }
987
988        private static void formatSeconds(Calendar cal, StringBuilder buf) {
989            formatTwoDigits(cal.get(Calendar.SECOND), buf);
990            if (cal.isSet(Calendar.MILLISECOND)) { // milliseconds
991                int n = cal.get(Calendar.MILLISECOND);
992                if (n != 0) {
993                    String ms = Integer.toString(n);
994                    while (ms.length() < 3) {
995                        ms = '0' + ms; // left 0 paddings.
996                    }
997                    buf.append('.');
998                    buf.append(ms);
999                }
1000            }
1001        }
1002
1003        /** formats time zone specifier. */
1004        private static void formatTimeZone(Calendar cal, StringBuilder buf) {
1005            TimeZone tz = cal.getTimeZone();
1006
1007            if (tz == null) {
1008                return;
1009            }
1010
1011            // otherwise print out normally.
1012            int offset = tz.getOffset(cal.getTime().getTime());
1013
1014            if (offset == 0) {
1015                buf.append('Z');
1016                return;
1017            }
1018
1019            if (offset >= 0) {
1020                buf.append('+');
1021            } else {
1022                buf.append('-');
1023                offset *= -1;
1024            }
1025
1026            offset /= 60 * 1000; // offset is in milli-seconds
1027
1028            formatTwoDigits(offset / 60, buf);
1029            buf.append(':');
1030            formatTwoDigits(offset % 60, buf);
1031        }
1032
1033        /** formats Integer into two-character-wide string. */
1034        private static void formatTwoDigits(int n, StringBuilder buf) {
1035            // n is always non-negative.
1036            if (n < 10) {
1037                buf.append('0');
1038            }
1039            buf.append(n);
1040        }
1041    }
1042}
1043