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