1 /*
2  * Copyright (c) 1997, 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 com.sun.xml.bind;
12
13 import java.math.BigDecimal;
14 import java.math.BigInteger;
15 import java.security.AccessController;
16 import java.security.PrivilegedAction;
17 import java.util.Calendar;
18 import java.util.Collections;
19 import java.util.GregorianCalendar;
20 import java.util.Map;
21 import java.util.TimeZone;
22 import java.util.WeakHashMap;
23
24 import javax.xml.bind.DatatypeConverter;
25 import javax.xml.bind.DatatypeConverterInterface;
26 import javax.xml.datatype.DatatypeConfigurationException;
27 import javax.xml.datatype.DatatypeFactory;
28 import javax.xml.namespace.NamespaceContext;
29 import javax.xml.namespace.QName;
30 import javax.xml.stream.XMLStreamException;
31 import javax.xml.stream.XMLStreamWriter;
32
33 /**
34  * This class is the JAXB RI's default implementation of the 
35  * {@link DatatypeConverterInterface}.
36  *
37  * <p>
38  * When client applications specify the use of the static print/parse
39  * methods in {@link DatatypeConverter}, it will delegate
40  * to this class.
41  *
42  * <p>
43  * This class is responsible for whitespace normalization.
44  *
45  * @author <ul><li>Ryan Shoemaker, Martin Grebac</li></ul>
46  * @since JAXB 1.0
47  * @deprecated in JAXB 2.2.4 - use javax.xml.bind.DatatypeConverterImpl instead
48  * or let us know why you can't
49  */

50 @Deprecated
51 public final class DatatypeConverterImpl implements DatatypeConverterInterface {
52
53     @Deprecated
54     public static final DatatypeConverterInterface theInstance = new DatatypeConverterImpl();
55     
56     protected DatatypeConverterImpl() {
57         // shall not be used
58     }
59
60     public static BigInteger _parseInteger(CharSequence s) {
61         return new BigInteger(removeOptionalPlus(WhiteSpaceProcessor.trim(s)).toString());
62     }
63
64     public static String _printInteger(BigInteger val) {
65         return val.toString();
66     }
67
68     /**
69      * Faster but less robust {@code String->int} conversion.
70      *
71      * Note that:
72      * <ol>
73      *  <li>XML Schema allows '+', but {@link Integer#valueOf(String)} is not.
74      *  <li>XML Schema allows leading and trailing (but not in-between) whitespaces.
75      *      {@link Integer#valueOf(String)} doesn't allow any.
76      * </ol>
77      */

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

152
153         if (s.equals("NaN")) {
154             return Float.NaN;
155         }
156         if (s.equals("INF")) {
157             return Float.POSITIVE_INFINITY;
158         }
159         if (s.equals("-INF")) {
160             return Float.NEGATIVE_INFINITY;
161         }
162
163         if (s.length() == 0
164                 || !isDigitOrPeriodOrSign(s.charAt(0))
165                 || !isDigitOrPeriodOrSign(s.charAt(s.length() - 1))) {
166             throw new NumberFormatException();
167         }
168
169         // these screening process is necessary due to the wobble of Float.valueOf method
170         return Float.parseFloat(s);
171     }
172
173     public static String _printFloat(float v) {
174         if (Float.isNaN(v)) {
175             return "NaN";
176         }
177         if (v == Float.POSITIVE_INFINITY) {
178             return "INF";
179         }
180         if (v == Float.NEGATIVE_INFINITY) {
181             return "-INF";
182         }
183         return String.valueOf(v);
184     }
185
186     public static double _parseDouble(CharSequence _val) {
187         String val = WhiteSpaceProcessor.trim(_val).toString();
188
189         if (val.equals("NaN")) {
190             return Double.NaN;
191         }
192         if (val.equals("INF")) {
193             return Double.POSITIVE_INFINITY;
194         }
195         if (val.equals("-INF")) {
196             return Double.NEGATIVE_INFINITY;
197         }
198
199         if (val.length() == 0
200                 || !isDigitOrPeriodOrSign(val.charAt(0))
201                 || !isDigitOrPeriodOrSign(val.charAt(val.length() - 1))) {
202             throw new NumberFormatException(val);
203         }
204
205
206         // these screening process is necessary due to the wobble of Float.valueOf method
207         return Double.parseDouble(val);
208     }
209
210     public static Boolean _parseBoolean(CharSequence literal) {
211         if (literal == null) {
212             return null;
213         }
214
215         int i = 0;
216         int len = literal.length();
217         char ch;
218         boolean value = false;
219
220         if (literal.length() <= 0) {
221             return null;
222         }
223
224         do {
225             ch = literal.charAt(i++);
226         } while (WhiteSpaceProcessor.isWhiteSpace(ch) && i < len);
227
228         int strIndex = 0;
229
230         switch (ch) {
231             case '1':
232                 value = true;
233                 break;
234             case '0':
235                 value = false;
236                 break;
237             case 't':
238                 String strTrue = "rue";
239                 do {
240                     ch = literal.charAt(i++);
241                 } while ((strTrue.charAt(strIndex++) == ch) && i < len && strIndex < 3);
242
243                 if (strIndex == 3) {
244                     value = true;
245                 } else {
246                     return false;
247                 }
248 //                    throw new IllegalArgumentException("String \"" + literal + "\" is not valid boolean value.");
249
250                 break;
251             case 'f':
252                 String strFalse = "alse";
253                 do {
254                     ch = literal.charAt(i++);
255                 } while ((strFalse.charAt(strIndex++) == ch) && i < len && strIndex < 4);
256
257
258                 if (strIndex == 4) {
259                     value = false;
260                 } else {
261                     return false;
262                 }
263 //                    throw new IllegalArgumentException("String \"" + literal + "\" is not valid boolean value.");
264
265                 break;
266         }
267
268         if (i < len) {
269             do {
270                 ch = literal.charAt(i++);
271             } while (WhiteSpaceProcessor.isWhiteSpace(ch) && i < len);
272         }
273
274         if (i == len) {
275             return value;
276         } else {
277             return null;
278         }
279 //            throw new IllegalArgumentException("String \"" + literal + "\" is not valid boolean value.");
280     }
281
282     public static String _printBoolean(boolean val) {
283         return val ? "true" : "false";
284     }
285
286     public static byte _parseByte(CharSequence literal) {
287         return (byte) _parseInt(literal);
288     }
289
290     public static String _printByte(byte val) {
291         return String.valueOf(val);
292     }
293
294     /**
295      * @return null if fails to convert.
296      */

297     public static QName _parseQName(CharSequence text, NamespaceContext nsc) {
298         int length = text.length();
299
300         // trim whitespace
301         int start = 0;
302         while (start < length && WhiteSpaceProcessor.isWhiteSpace(text.charAt(start))) {
303             start++;
304         }
305
306         int end = length;
307         while (end > start && WhiteSpaceProcessor.isWhiteSpace(text.charAt(end - 1))) {
308             end--;
309         }
310
311         if (end == start) {
312             throw new IllegalArgumentException("input is empty");
313         }
314
315
316         String uri;
317         String localPart;
318         String prefix;
319
320         // search ':'
321         int idx = start + 1;    // no point in searching the first char. that's not valid.
322         while (idx < end && text.charAt(idx) != ':') {
323             idx++;
324         }
325
326         if (idx == end) {
327             uri = nsc.getNamespaceURI("");
328             localPart = text.subSequence(start, end).toString();
329             prefix = "";
330         } else {
331             // Prefix exists, check everything
332             prefix = text.subSequence(start, idx).toString();
333             localPart = text.subSequence(idx + 1, end).toString();
334             uri = nsc.getNamespaceURI(prefix);
335             // uri can never be null according to javadoc,
336             // but some users reported that there are implementations that return null.
337             if (uri == null || uri.length() == 0) // crap. the NamespaceContext interface is broken.
338             // error: unbound prefix
339             {
340                 throw new IllegalArgumentException("prefix " + prefix + " is not bound to a namespace");
341             }
342         }
343
344         return new QName(uri, localPart, prefix);
345     }
346
347     public static GregorianCalendar _parseDateTime(CharSequence s) {
348         String val = WhiteSpaceProcessor.trim(s).toString();
349         return getDatatypeFactory().newXMLGregorianCalendar(val).toGregorianCalendar();
350     }
351
352     public static String _printDateTime(Calendar val) {
353         return CalendarFormatter.doFormat("%Y-%M-%DT%h:%m:%s%z", val);
354     }
355
356     public static String _printDate(Calendar val) {
357         return CalendarFormatter.doFormat((new StringBuilder("%Y-%M-%D").append("%z")).toString(),val);
358     }
359
360     public static String _printInt(int val) {
361         return String.valueOf(val);
362     }
363
364     public static String _printLong(long val) {
365         return String.valueOf(val);
366     }
367
368     public static String _printDecimal(BigDecimal val) {
369         return val.toPlainString();
370     }
371
372     public static String _printDouble(double v) {
373         if (Double.isNaN(v)) {
374             return "NaN";
375         }
376         if (v == Double.POSITIVE_INFINITY) {
377             return "INF";
378         }
379         if (v == Double.NEGATIVE_INFINITY) {
380             return "-INF";
381         }
382         return String.valueOf(v);
383     }
384
385     public static String _printQName(QName val, NamespaceContext nsc) {
386         // Double-check
387         String qname;
388         String prefix = nsc.getPrefix(val.getNamespaceURI());
389         String localPart = val.getLocalPart();
390
391         if (prefix == null || prefix.length() == 0) { // be defensive
392             qname = localPart;
393         } else {
394             qname = prefix + ':' + localPart;
395         }
396
397         return qname;
398     }
399
400 // base64 decoder
401     private static final byte[] decodeMap = initDecodeMap();
402     private static final byte PADDING = 127;
403
404     private static byte[] initDecodeMap() {
405         byte[] map = new byte[128];
406         int i;
407         for (i = 0; i < 128; i++) {
408             map[i] = -1;
409         }
410
411         for (i = 'A'; i <= 'Z'; i++) {
412             map[i] = (byte) (i - 'A');
413         }
414         for (i = 'a'; i <= 'z'; i++) {
415             map[i] = (byte) (i - 'a' + 26);
416         }
417         for (i = '0'; i <= '9'; i++) {
418             map[i] = (byte) (i - '0' + 52);
419         }
420         map['+'] = 62;
421         map['/'] = 63;
422         map['='] = PADDING;
423
424         return map;
425     }
426
427     /**
428      * computes the length of binary data speculatively.
429      *
430      * <p>
431      * Our requirement is to create byte[] of the exact length to store the binary data.
432      * If we do this in a straight-forward way, it takes two passes over the data.
433      * Experiments show that this is a non-trivial overhead (35% or so is spent on
434      * the first pass in calculating the length.)
435      *
436      * <p>
437      * So the approach here is that we compute the length speculatively, without looking
438      * at the whole contents. The obtained speculative value is never less than the
439      * actual length of the binary data, but it may be bigger. So if the speculation
440      * goes wrong, we'll pay the cost of reallocation and buffer copying.
441      *
442      * <p>
443      * If the base64 text is tightly packed with no indentation nor illegal char
444      * (like what most web services produce), then the speculation of this method
445      * will be correct, so we get the performance benefit.
446      */

447     private static int guessLength(String text) {
448         final int len = text.length();
449
450         // compute the tail '=' chars
451         int j = len - 1;
452         for (; j >= 0; j--) {
453             byte code = decodeMap[text.charAt(j)];
454             if (code == PADDING) {
455                 continue;
456             }
457             if (code == -1) // most likely this base64 text is indented. go with the upper bound
458             {
459                 return text.length() / 4 * 3;
460             }
461             break;
462         }
463
464         j++;    // text.charAt(j) is now at some base64 char, so +1 to make it the size
465         int padSize = len - j;
466         if (padSize > 2) // something is wrong with base64. be safe and go with the upper bound
467         {
468             return text.length() / 4 * 3;
469         }
470
471         // so far this base64 looks like it's unindented tightly packed base64.
472         // take a chance and create an array with the expected size
473         return text.length() / 4 * 3 - padSize;
474     }
475
476     /**
477      * @param text
478      *      base64Binary data is likely to be long, and decoding requires
479      *      each character to be accessed twice (once for counting length, another
480      *      for decoding.)
481      *
482      *      A benchmark showed that taking {@link String} is faster, presumably
483      *      because JIT can inline a lot of string access (with data of 1K chars, it was twice as fast)
484      */

485     public static byte[] _parseBase64Binary(String text) {
486         final int buflen = guessLength(text);
487         final byte[] out = new byte[buflen];
488         int o = 0;
489
490         final int len = text.length();
491         int i;
492
493         final byte[] quadruplet = new byte[4];
494         int q = 0;
495
496         // convert each quadruplet to three bytes.
497         for (i = 0; i < len; i++) {
498             char ch = text.charAt(i);
499             byte v = decodeMap[ch];
500
501             if (v != -1) {
502                 quadruplet[q++] = v;
503             }
504
505             if (q == 4) {
506                 // quadruplet is now filled.
507                 out[o++] = (byte) ((quadruplet[0] << 2) | (quadruplet[1] >> 4));
508                 if (quadruplet[2] != PADDING) {
509                     out[o++] = (byte) ((quadruplet[1] << 4) | (quadruplet[2] >> 2));
510                 }
511                 if (quadruplet[3] != PADDING) {
512                     out[o++] = (byte) ((quadruplet[2] << 6) | (quadruplet[3]));
513                 }
514                 q = 0;
515             }
516         }
517
518         if (buflen == o) // speculation worked out to be OK
519         {
520             return out;
521         }
522
523         // we overestimated, so need to create a new buffer
524         byte[] nb = new byte[o];
525         System.arraycopy(out, 0, nb, 0, o);
526         return nb;
527     }
528     private static final char[] encodeMap = initEncodeMap();
529
530     private static char[] initEncodeMap() {
531         char[] map = new char[64];
532         int i;
533         for (i = 0; i < 26; i++) {
534             map[i] = (char) ('A' + i);
535         }
536         for (i = 26; i < 52; i++) {
537             map[i] = (char) ('a' + (i - 26));
538         }
539         for (i = 52; i < 62; i++) {
540             map[i] = (char) ('0' + (i - 52));
541         }
542         map[62] = '+';
543         map[63] = '/';
544
545         return map;
546     }
547
548     public static char encode(int i) {
549         return encodeMap[i & 0x3F];
550     }
551
552     public static byte encodeByte(int i) {
553         return (byte) encodeMap[i & 0x3F];
554     }
555
556     public static String _printBase64Binary(byte[] input) {
557         return _printBase64Binary(input, 0, input.length);
558     }
559
560     public static String _printBase64Binary(byte[] input, int offset, int len) {
561         char[] buf = new char[((len + 2) / 3) * 4];
562         int ptr = _printBase64Binary(input, offset, len, buf, 0);
563         assert ptr == buf.length;
564         return new String(buf);
565     }
566
567     /**
568      * Encodes a byte array into a char array by doing base64 encoding.
569      *
570      * The caller must supply a big enough buffer.
571      *
572      * @return
573      *      the value of {@code ptr+((len+2)/3)*4}, which is the new offset
574      *      in the output buffer where the further bytes should be placed.
575      */

576     public static int _printBase64Binary(byte[] input, int offset, int len, char[] buf, int ptr) {
577         // encode elements until only 1 or 2 elements are left to encode
578         int remaining = len;
579         int i;
580         for (i = offset;remaining >= 3; remaining -= 3, i += 3) {
581             buf[ptr++] = encode(input[i] >> 2);
582             buf[ptr++] = encode(
583                     ((input[i] & 0x3) << 4)
584                     | ((input[i + 1] >> 4) & 0xF));
585             buf[ptr++] = encode(
586                     ((input[i + 1] & 0xF) << 2)
587                     | ((input[i + 2] >> 6) & 0x3));
588             buf[ptr++] = encode(input[i + 2] & 0x3F);
589         }
590         // encode when exactly 1 element (left) to encode
591         if (remaining == 1) {
592             buf[ptr++] = encode(input[i] >> 2);
593             buf[ptr++] = encode(((input[i]) & 0x3) << 4);
594             buf[ptr++] = '=';
595             buf[ptr++] = '=';
596         }
597         // encode when exactly 2 elements (left) to encode
598         if (remaining == 2) {
599             buf[ptr++] = encode(input[i] >> 2);
600             buf[ptr++] = encode(((input[i] & 0x3) << 4)
601                     | ((input[i + 1] >> 4) & 0xF));
602             buf[ptr++] = encode((input[i + 1] & 0xF) << 2);
603             buf[ptr++] = '=';
604         }
605         return ptr;
606     }
607
608     public static void _printBase64Binary(byte[] input, int offset, int len, XMLStreamWriter output) throws XMLStreamException {
609         int remaining = len;
610         int i;
611         char[] buf = new char[4];
612         
613         for (i = offset; remaining >= 3; remaining -= 3, i += 3) {
614             buf[0] = encode(input[i] >> 2);
615             buf[1] = encode(
616                     ((input[i] & 0x3) << 4)
617                     | ((input[i + 1] >> 4) & 0xF));
618             buf[2] = encode(
619                     ((input[i + 1] & 0xF) << 2)
620                     | ((input[i + 2] >> 6) & 0x3));
621             buf[3] = encode(input[i + 2] & 0x3F);
622             output.writeCharacters(buf, 0, 4);
623         }
624         // encode when exactly 1 element (left) to encode
625         if (remaining == 1) {
626             buf[0] = encode(input[i] >> 2);
627             buf[1] = encode(((input[i]) & 0x3) << 4);
628             buf[2] = '=';
629             buf[3] = '=';
630             output.writeCharacters(buf, 0, 4);
631         }
632         // encode when exactly 2 elements (left) to encode
633         if (remaining == 2) {
634             buf[0] = encode(input[i] >> 2);
635             buf[1] = encode(((input[i] & 0x3) << 4)
636                     | ((input[i + 1] >> 4) & 0xF));
637             buf[2] = encode((input[i + 1] & 0xF) << 2);
638             buf[3] = '=';
639             output.writeCharacters(buf, 0, 4);
640         }
641     }
642     
643     /**
644      * Encodes a byte array into another byte array by first doing base64 encoding
645      * then encoding the result in ASCII.
646      *
647      * The caller must supply a big enough buffer.
648      *
649      * @return
650      *      the value of {@code ptr+((len+2)/3)*4}, which is the new offset
651      *      in the output buffer where the further bytes should be placed.
652      */

653     public static int _printBase64Binary(byte[] input, int offset, int len, byte[] out, int ptr) {
654         byte[] buf = out;
655         int remaining = len;
656         int i;
657         for (i=offset; remaining >= 3; remaining -= 3, i += 3 ) {
658             buf[ptr++] = encodeByte(input[i]>>2);
659             buf[ptr++] = encodeByte(
660                         ((input[i]&0x3)<<4) |
661                         ((input[i+1]>>4)&0xF));
662             buf[ptr++] = encodeByte(
663                         ((input[i+1]&0xF)<<2)|
664                         ((input[i+2]>>6)&0x3));
665             buf[ptr++] = encodeByte(input[i+2]&0x3F);
666         }
667         // encode when exactly 1 element (left) to encode
668         if (remaining == 1) {
669             buf[ptr++] = encodeByte(input[i]>>2);
670             buf[ptr++] = encodeByte(((input[i])&0x3)<<4);
671             buf[ptr++] = '=';
672             buf[ptr++] = '=';
673         }
674         // encode when exactly 2 elements (left) to encode
675         if (remaining == 2) {
676             buf[ptr++] = encodeByte(input[i]>>2);
677             buf[ptr++] = encodeByte(
678                         ((input[i]&0x3)<<4) |
679                         ((input[i+1]>>4)&0xF));
680             buf[ptr++] = encodeByte((input[i+1]&0xF)<<2);
681             buf[ptr++] = '=';
682         }
683
684         return ptr;
685     }
686
687     private static CharSequence removeOptionalPlus(CharSequence s) {
688         int len = s.length();
689
690         if (len <= 1 || s.charAt(0) != '+') {
691             return s;
692         }
693
694         s = s.subSequence(1, len);
695         char ch = s.charAt(0);
696         if ('0' <= ch && ch <= '9') {
697             return s;
698         }
699         if ('.' == ch) {
700             return s;
701         }
702
703         throw new NumberFormatException();
704     }
705
706     private static boolean isDigitOrPeriodOrSign(char ch) {
707         if ('0' <= ch && ch <= '9') {
708             return true;
709         }
710         if (ch == '+' || ch == '-' || ch == '.') {
711             return true;
712         }
713         return false;
714     }
715
716     private static final Map<ClassLoader, DatatypeFactory> DF_CACHE = Collections.synchronizedMap(new WeakHashMap<ClassLoader, DatatypeFactory>());
717
718     public static DatatypeFactory getDatatypeFactory() {
719         ClassLoader tccl = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
720             public ClassLoader run() {
721                 return Thread.currentThread().getContextClassLoader();
722             }
723         });
724         DatatypeFactory df = DF_CACHE.get(tccl);
725         if (df == null) {
726             synchronized (DatatypeConverterImpl.class) {
727                 df = DF_CACHE.get(tccl);
728                 if (df == null) { // to prevent multiple initialization
729                     try {
730                         df = DatatypeFactory.newInstance();
731                     } catch (DatatypeConfigurationException e) {
732                         throw new Error(Messages.FAILED_TO_INITIALE_DATATYPE_FACTORY.format(),e);
733                     }
734                     DF_CACHE.put(tccl, df);
735                 }
736             }
737         }
738         return df;
739     }
740
741     private static final class CalendarFormatter {
742
743         public static String doFormat(String format, Calendar cal) throws IllegalArgumentException {
744             int fidx = 0;
745             int flen = format.length();
746             StringBuilder buf = new StringBuilder();
747
748             while (fidx < flen) {
749                 char fch = format.charAt(fidx++);
750
751                 if (fch != '%') {  // not a meta character
752                     buf.append(fch);
753                     continue;
754                 }
755
756                 // seen meta character. we don't do error check against the format
757                 switch (format.charAt(fidx++)) {
758                     case 'Y': // year
759                         formatYear(cal, buf);
760                         break;
761
762                     case 'M': // month
763                         formatMonth(cal, buf);
764                         break;
765
766                     case 'D': // days
767                         formatDays(cal, buf);
768                         break;
769
770                     case 'h': // hours
771                         formatHours(cal, buf);
772                         break;
773
774                     case 'm': // minutes
775                         formatMinutes(cal, buf);
776                         break;
777
778                     case 's': // parse seconds.
779                         formatSeconds(cal, buf);
780                         break;
781
782                     case 'z': // time zone
783                         formatTimeZone(cal, buf);
784                         break;
785
786                     default:
787                         // illegal meta character. impossible.
788                         throw new InternalError();
789                 }
790             }
791
792             return buf.toString();
793         }
794
795         private static void formatYear(Calendar cal, StringBuilder buf) {
796             int year = cal.get(Calendar.YEAR);
797
798             String s;
799             if (year <= 0) // negative value
800             {
801                 s = Integer.toString(1 - year);
802             } else // positive value
803             {
804                 s = Integer.toString(year);
805             }
806
807             while (s.length() < 4) {
808                 s = '0' + s;
809             }
810             if (year <= 0) {
811                 s = '-' + s;
812             }
813
814             buf.append(s);
815         }
816
817         private static void formatMonth(Calendar cal, StringBuilder buf) {
818             formatTwoDigits(cal.get(Calendar.MONTH) + 1, buf);
819         }
820
821         private static void formatDays(Calendar cal, StringBuilder buf) {
822             formatTwoDigits(cal.get(Calendar.DAY_OF_MONTH), buf);
823         }
824
825         private static void formatHours(Calendar cal, StringBuilder buf) {
826             formatTwoDigits(cal.get(Calendar.HOUR_OF_DAY), buf);
827         }
828
829         private static void formatMinutes(Calendar cal, StringBuilder buf) {
830             formatTwoDigits(cal.get(Calendar.MINUTE), buf);
831         }
832
833         private static void formatSeconds(Calendar cal, StringBuilder buf) {
834             formatTwoDigits(cal.get(Calendar.SECOND), buf);
835             if (cal.isSet(Calendar.MILLISECOND)) { // milliseconds
836                 int n = cal.get(Calendar.MILLISECOND);
837                 if (n != 0) {
838                     String ms = Integer.toString(n);
839                     while (ms.length() < 3) {
840                         ms = '0' + ms; // left 0 paddings.
841                     }
842                     buf.append('.');
843                     buf.append(ms);
844                 }
845             }
846         }
847
848         /** formats time zone specifier. */
849         private static void formatTimeZone(Calendar cal, StringBuilder buf) {
850             TimeZone tz = cal.getTimeZone();
851
852             if (tz == null) {
853                 return;
854             }
855
856             // otherwise print out normally.
857             int offset = tz.getOffset(cal.getTime().getTime());
858
859             if (offset == 0) {
860                 buf.append('Z');
861                 return;
862             }
863
864             if (offset >= 0) {
865                 buf.append('+');
866             } else {
867                 buf.append('-');
868                 offset *= -1;
869             }
870
871             offset /= 60 * 1000; // offset is in milli-seconds
872
873             formatTwoDigits(offset / 60, buf);
874             buf.append(':');
875             formatTwoDigits(offset % 60, buf);
876         }
877
878         /** formats Integer into two-character-wide string. */
879         private static void formatTwoDigits(int n, StringBuilder buf) {
880             // n is always non-negative.
881             if (n < 10) {
882                 buf.append('0');
883             }
884             buf.append(n);
885         }
886     }
887
888     // DEPRECATED METHODS, KEPT FOR JAXB1 GENERATED CLASSES COMPATIBILITY, WILL BE REMOVED IN FUTURE
889
890     @Deprecated
891     public String parseString(String lexicalXSDString) {
892         return lexicalXSDString;
893     }
894
895     @Deprecated
896     public BigInteger parseInteger(String lexicalXSDInteger) {
897         return _parseInteger(lexicalXSDInteger);
898     }
899
900     @Deprecated
901     public String printInteger(BigInteger val) {
902         return _printInteger(val);
903     }
904
905     @Deprecated
906     public int parseInt(String s) {
907         return _parseInt(s);
908     }
909
910     @Deprecated
911     public long parseLong(String lexicalXSLong) {
912         return _parseLong(lexicalXSLong);
913     }
914
915     @Deprecated
916     public short parseShort(String lexicalXSDShort) {
917         return _parseShort(lexicalXSDShort);
918     }
919
920     @Deprecated
921     public String printShort(short val) {
922         return _printShort(val);
923     }
924
925     @Deprecated
926     public BigDecimal parseDecimal(String content) {
927         return _parseDecimal(content);
928     }
929
930     @Deprecated
931     public float parseFloat(String lexicalXSDFloat) {
932         return _parseFloat(lexicalXSDFloat);
933     }
934
935     @Deprecated
936     public String printFloat(float v) {
937         return _printFloat(v);
938     }
939
940     @Deprecated
941     public double parseDouble(String lexicalXSDDouble) {
942         return _parseDouble(lexicalXSDDouble);
943     }
944
945     @Deprecated
946     public boolean parseBoolean(String lexicalXSDBoolean) {
947         Boolean b = _parseBoolean(lexicalXSDBoolean);
948         return (b == null) ? false : b.booleanValue();
949     }
950
951     @Deprecated
952     public String printBoolean(boolean val) {
953         return val ? "true" : "false";
954     }
955
956     @Deprecated
957     public byte parseByte(String lexicalXSDByte) {
958         return _parseByte(lexicalXSDByte);
959     }
960
961     @Deprecated
962     public String printByte(byte val) {
963         return _printByte(val);
964     }
965
966     @Deprecated
967     public QName parseQName(String lexicalXSDQName, NamespaceContext nsc) {
968         return _parseQName(lexicalXSDQName, nsc);
969     }
970
971     @Deprecated
972     public Calendar parseDateTime(String lexicalXSDDateTime) {
973         return _parseDateTime(lexicalXSDDateTime);
974     }
975
976     @Deprecated
977     public String printDateTime(Calendar val) {
978         return _printDateTime(val);
979     }
980
981     @Deprecated
982     public byte[] parseBase64Binary(String lexicalXSDBase64Binary) {
983         return _parseBase64Binary(lexicalXSDBase64Binary);
984     }
985
986     @Deprecated
987     public byte[] parseHexBinary(String s) {
988         final int len = s.length();
989
990         // "111" is not a valid hex encoding.
991         if (len % 2 != 0) {
992             throw new IllegalArgumentException("hexBinary needs to be even-length: " + s);
993         }
994
995         byte[] out = new byte[len / 2];
996
997         for (int i = 0; i < len; i += 2) {
998             int h = hexToBin(s.charAt(i));
999             int l = hexToBin(s.charAt(i + 1));
1000             if (h == -1 || l == -1) {
1001                 throw new IllegalArgumentException("contains illegal character for hexBinary: " + s);
1002             }
1003
1004             out[i / 2] = (byte) (h * 16 + l);
1005         }
1006
1007         return out;
1008     }
1009
1010     @Deprecated
1011     private static int hexToBin(char ch) {
1012         if ('0' <= ch && ch <= '9') {
1013             return ch - '0';
1014         }
1015         if ('A' <= ch && ch <= 'F') {
1016             return ch - 'A' + 10;
1017         }
1018         if ('a' <= ch && ch <= 'f') {
1019             return ch - 'a' + 10;
1020         }
1021         return -1;
1022     }
1023
1024     @Deprecated
1025     private static final char[] hexCode = "0123456789ABCDEF".toCharArray();
1026
1027     @Deprecated
1028     public String printHexBinary(byte[] data) {
1029         StringBuilder r = new StringBuilder(data.length * 2);
1030         for (byte b : data) {
1031             r.append(hexCode[(b >> 4) & 0xF]);
1032             r.append(hexCode[(b & 0xF)]);
1033         }
1034         return r.toString();
1035     }
1036
1037     @Deprecated
1038     public long parseUnsignedInt(String lexicalXSDUnsignedInt) {
1039         return _parseLong(lexicalXSDUnsignedInt);
1040     }
1041
1042     @Deprecated
1043     public String printUnsignedInt(long val) {
1044         return _printLong(val);
1045     }
1046
1047     @Deprecated
1048     public int parseUnsignedShort(String lexicalXSDUnsignedShort) {
1049         return _parseInt(lexicalXSDUnsignedShort);
1050     }
1051
1052     @Deprecated
1053     public Calendar parseTime(String lexicalXSDTime) {
1054         return getDatatypeFactory().newXMLGregorianCalendar(lexicalXSDTime).toGregorianCalendar();
1055     }
1056
1057     @Deprecated
1058     public String printTime(Calendar val) {
1059         return CalendarFormatter.doFormat("%h:%m:%s%z", val);
1060     }
1061
1062     @Deprecated
1063     public Calendar parseDate(String lexicalXSDDate) {
1064         return getDatatypeFactory().newXMLGregorianCalendar(lexicalXSDDate).toGregorianCalendar();
1065     }
1066
1067     @Deprecated
1068     public String printDate(Calendar val) {
1069         return _printDate(val);
1070     }
1071
1072     @Deprecated
1073     public String parseAnySimpleType(String lexicalXSDAnySimpleType) {
1074         return lexicalXSDAnySimpleType;
1075     }
1076
1077     @Deprecated
1078     public String printString(String val) {
1079         return val;
1080     }
1081
1082     @Deprecated
1083     public String printInt(int val) {
1084         return _printInt(val);
1085     }
1086
1087     @Deprecated
1088     public String printLong(long val) {
1089         return _printLong(val);
1090     }
1091
1092     @Deprecated
1093     public String printDecimal(BigDecimal val) {
1094         return _printDecimal(val);
1095     }
1096
1097     @Deprecated
1098     public String printDouble(double v) {
1099         return _printDouble(v);
1100     }
1101
1102     @Deprecated
1103     public String printQName(QName val, NamespaceContext nsc) {
1104         return _printQName(val, nsc);
1105     }
1106
1107     @Deprecated
1108     public String printBase64Binary(byte[] val) {
1109         return _printBase64Binary(val);
1110     }
1111
1112     @Deprecated
1113     public String printUnsignedShort(int val) {
1114         return String.valueOf(val);
1115     }
1116
1117     @Deprecated
1118     public String printAnySimpleType(String val) {
1119         return val;
1120     }
1121     
1122 }
1123