001: /*
002: * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.xml.internal.bind;
027:
028: import java.math.BigDecimal;
029: import java.math.BigInteger;
030: import java.util.Calendar;
031: import java.util.GregorianCalendar;
032: import java.util.TimeZone;
033:
034: import javax.xml.bind.DatatypeConverter;
035: import javax.xml.bind.DatatypeConverterInterface;
036: import javax.xml.datatype.DatatypeConfigurationException;
037: import javax.xml.datatype.DatatypeFactory;
038: import javax.xml.namespace.NamespaceContext;
039: import javax.xml.namespace.QName;
040:
041: import com.sun.xml.internal.bind.v2.TODO;
042:
043: /**
044: * This class is the JAXB RI's default implementation of the
045: * {@link DatatypeConverterInterface}.
046: *
047: * <p>
048: * When client apps specify the use of the static print/parse
049: * methods in {@link DatatypeConverter}, it will delegate
050: * to this class.
051: *
052: * <p>
053: * This class is responsible for whitespace normalization.
054: *
055: * @author <ul><li>Ryan Shoemaker, Sun Microsystems, Inc.</li></ul>
056: * @version $Revision: 1.8 $
057: * @since JAXB1.0
058: */
059: public final class DatatypeConverterImpl implements
060: DatatypeConverterInterface {
061:
062: /**
063: * To avoid re-creating instances, we cache one instance.
064: */
065: public static final DatatypeConverterInterface theInstance = new DatatypeConverterImpl();
066:
067: protected DatatypeConverterImpl() {
068: }
069:
070: public String parseString(String lexicalXSDString) {
071: return lexicalXSDString;
072: }
073:
074: public BigInteger parseInteger(String lexicalXSDInteger) {
075: return _parseInteger(lexicalXSDInteger);
076: }
077:
078: public static BigInteger _parseInteger(CharSequence s) {
079: return new BigInteger(removeOptionalPlus(
080: WhiteSpaceProcessor.trim(s)).toString());
081: }
082:
083: public String printInteger(BigInteger val) {
084: return _printInteger(val);
085: }
086:
087: public static String _printInteger(BigInteger val) {
088: return val.toString();
089: }
090:
091: public int parseInt(String s) {
092: return _parseInt(s);
093: }
094:
095: /**
096: * Faster but less robust String->int conversion.
097: *
098: * Note that:
099: * <ol>
100: * <li>XML Schema allows '+', but {@link Integer#valueOf(String)} is not.
101: * <li>XML Schema allows leading and trailing (but not in-between) whitespaces..
102: * {@link Integer#valueOf(String)} doesn't allow any.
103: * </ol>
104: */
105: public static int _parseInt(CharSequence s) {
106: int len = s.length();
107: int sign = 1;
108:
109: int r = 0;
110:
111: for (int i = 0; i < len; i++) {
112: char ch = s.charAt(i);
113: if (WhiteSpaceProcessor.isWhiteSpace(ch)) {
114: // skip whitespace
115: } else if ('0' <= ch && ch <= '9') {
116: r = r * 10 + (ch - '0');
117: } else if (ch == '-') {
118: sign = -1;
119: } else if (ch == '+') {
120: ; // noop
121: } else
122: throw new NumberFormatException("Not a number: " + s);
123: }
124:
125: return r * sign;
126: }
127:
128: public long parseLong(String lexicalXSLong) {
129: return _parseLong(lexicalXSLong);
130: }
131:
132: public static long _parseLong(CharSequence s) {
133: return Long.valueOf(removeOptionalPlus(
134: WhiteSpaceProcessor.trim(s)).toString());
135: }
136:
137: public short parseShort(String lexicalXSDShort) {
138: return _parseShort(lexicalXSDShort);
139: }
140:
141: public static final short _parseShort(CharSequence s) {
142: return (short) _parseInt(s);
143: }
144:
145: public String printShort(short val) {
146: return _printShort(val);
147: }
148:
149: public static String _printShort(short val) {
150: return String.valueOf(val);
151: }
152:
153: public BigDecimal parseDecimal(String content) {
154: return _parseDecimal(content);
155: }
156:
157: public static BigDecimal _parseDecimal(CharSequence content) {
158: content = WhiteSpaceProcessor.trim(content);
159:
160: return new BigDecimal(content.toString());
161:
162: // from purely XML Schema perspective,
163: // this implementation has a problem, since
164: // in xs:decimal "1.0" and "1" is equal whereas the above
165: // code will return different values for those two forms.
166: //
167: // the code was originally using com.sun.msv.datatype.xsd.NumberType.load,
168: // but a profiling showed that the process of normalizing "1.0" into "1"
169: // could take non-trivial time.
170: //
171: // also, from the user's point of view, one might be surprised if
172: // 1 (not 1.0) is returned from "1.000"
173: }
174:
175: public float parseFloat(String lexicalXSDFloat) {
176: return _parseFloat(lexicalXSDFloat);
177: }
178:
179: public static float _parseFloat(CharSequence _val) {
180: String s = WhiteSpaceProcessor.trim(_val).toString();
181: /* Incompatibilities of XML Schema's float "xfloat" and Java's float "jfloat"
182:
183: * jfloat.valueOf ignores leading and trailing whitespaces,
184: whereas this is not allowed in xfloat.
185: * jfloat.valueOf allows "float type suffix" (f, F) to be
186: appended after float literal (e.g., 1.52e-2f), whereare
187: this is not the case of xfloat.
188:
189: gray zone
190: ---------
191: * jfloat allows ".523". And there is no clear statement that mentions
192: this case in xfloat. Although probably this is allowed.
193: *
194: */
195:
196: if (s.equals("NaN"))
197: return Float.NaN;
198: if (s.equals("INF"))
199: return Float.POSITIVE_INFINITY;
200: if (s.equals("-INF"))
201: return Float.NEGATIVE_INFINITY;
202:
203: if (s.length() == 0 || !isDigitOrPeriodOrSign(s.charAt(0))
204: || !isDigitOrPeriodOrSign(s.charAt(s.length() - 1)))
205: throw new NumberFormatException();
206:
207: // these screening process is necessary due to the wobble of Float.valueOf method
208: return Float.parseFloat(s);
209: }
210:
211: public String printFloat(float v) {
212: return _printFloat(v);
213: }
214:
215: public static String _printFloat(float v) {
216: if (v == Float.NaN)
217: return "NaN";
218: if (v == Float.POSITIVE_INFINITY)
219: return "INF";
220: if (v == Float.NEGATIVE_INFINITY)
221: return "-INF";
222: return String.valueOf(v);
223: }
224:
225: public double parseDouble(String lexicalXSDDouble) {
226: return _parseDouble(lexicalXSDDouble);
227: }
228:
229: public static double _parseDouble(CharSequence _val) {
230: String val = WhiteSpaceProcessor.trim(_val).toString();
231:
232: if (val.equals("NaN"))
233: return Double.NaN;
234: if (val.equals("INF"))
235: return Double.POSITIVE_INFINITY;
236: if (val.equals("-INF"))
237: return Double.NEGATIVE_INFINITY;
238:
239: if (val.length() == 0 || !isDigitOrPeriodOrSign(val.charAt(0))
240: || !isDigitOrPeriodOrSign(val.charAt(val.length() - 1)))
241: throw new NumberFormatException(val);
242:
243: // these screening process is necessary due to the wobble of Float.valueOf method
244: return Double.parseDouble(val);
245: }
246:
247: public boolean parseBoolean(String lexicalXSDBoolean) {
248: return _parseBoolean(lexicalXSDBoolean);
249: }
250:
251: public static boolean _parseBoolean(CharSequence literal) {
252: int i = 0;
253: int len = literal.length();
254: char ch;
255: do {
256: ch = literal.charAt(i++);
257: } while (WhiteSpaceProcessor.isWhiteSpace(ch) && i < len);
258:
259: // if we are strict about errors, check i==len. and report an error
260:
261: if (ch == 't' || ch == '1')
262: return true;
263: if (ch == 'f' || ch == '0')
264: return false;
265: TODO.checkSpec("issue #42");
266: return false;
267: }
268:
269: public String printBoolean(boolean val) {
270: return val ? "true" : "false";
271: }
272:
273: public static String _printBoolean(boolean val) {
274: return val ? "true" : "false";
275: }
276:
277: public byte parseByte(String lexicalXSDByte) {
278: return _parseByte(lexicalXSDByte);
279: }
280:
281: public static byte _parseByte(CharSequence literal) {
282: return (byte) _parseInt(literal);
283: }
284:
285: public String printByte(byte val) {
286: return _printByte(val);
287: }
288:
289: public static String _printByte(byte val) {
290: return String.valueOf(val);
291: }
292:
293: public QName parseQName(String lexicalXSDQName, NamespaceContext nsc) {
294: return _parseQName(lexicalXSDQName, nsc);
295: }
296:
297: /**
298: * @return null if fails to convert.
299: */
300: public static QName _parseQName(CharSequence text,
301: NamespaceContext nsc) {
302: int length = text.length();
303:
304: // trim whitespace
305: int start = 0;
306: while (start < length
307: && WhiteSpaceProcessor.isWhiteSpace(text.charAt(start)))
308: start++;
309:
310: int end = length;
311: while (end > start
312: && WhiteSpaceProcessor.isWhiteSpace(text
313: .charAt(end - 1)))
314: end--;
315:
316: if (end == start)
317: throw new IllegalArgumentException("input is empty");
318:
319: String uri;
320: String localPart;
321: String prefix;
322:
323: // search ':'
324: int idx = start + 1; // no point in searching the first char. that's not valid.
325: while (idx < end && text.charAt(idx) != ':')
326: idx++;
327:
328: if (idx == end) {
329: uri = nsc.getNamespaceURI("");
330: localPart = text.subSequence(start, end).toString();
331: prefix = "";
332: } else {
333: // Prefix exists, check everything
334: prefix = text.subSequence(start, idx).toString();
335: localPart = text.subSequence(idx + 1, end).toString();
336: uri = nsc.getNamespaceURI(prefix);
337: // uri can never be null according to javadoc,
338: // but some users reported that there are implementations that return null.
339: if (uri == null || uri.length() == 0) // crap. the NamespaceContext interface is broken.
340: // error: unbound prefix
341: throw new IllegalArgumentException("prefix " + prefix
342: + " is not bound to a namespace");
343: }
344:
345: return new QName(uri, localPart, prefix);
346: }
347:
348: public Calendar parseDateTime(String lexicalXSDDateTime) {
349: return _parseDateTime(lexicalXSDDateTime);
350: }
351:
352: public static GregorianCalendar _parseDateTime(CharSequence s) {
353: String val = WhiteSpaceProcessor.trim(s).toString();
354: return datatypeFactory.newXMLGregorianCalendar(val)
355: .toGregorianCalendar();
356: }
357:
358: public String printDateTime(Calendar val) {
359: return _printDateTime(val);
360: }
361:
362: public static String _printDateTime(Calendar val) {
363: return CalendarFormatter.doFormat("%Y-%M-%DT%h:%m:%s%z", val);
364: }
365:
366: public byte[] parseBase64Binary(String lexicalXSDBase64Binary) {
367: return _parseBase64Binary(lexicalXSDBase64Binary);
368: }
369:
370: public byte[] parseHexBinary(String s) {
371: final int len = s.length();
372:
373: // "111" is not a valid hex encoding.
374: if (len % 2 != 0)
375: return null;
376:
377: byte[] out = new byte[len / 2];
378:
379: for (int i = 0; i < len; i += 2) {
380: int h = hexToBin(s.charAt(i));
381: int l = hexToBin(s.charAt(i + 1));
382: if (h == -1 || l == -1)
383: return null; // illegal character
384:
385: out[i / 2] = (byte) (h * 16 + l);
386: }
387:
388: return out;
389: }
390:
391: private static int hexToBin(char ch) {
392: if ('0' <= ch && ch <= '9')
393: return ch - '0';
394: if ('A' <= ch && ch <= 'F')
395: return ch - 'A' + 10;
396: if ('a' <= ch && ch <= 'f')
397: return ch - 'a' + 10;
398: return -1;
399: }
400:
401: private static final char[] hexCode = "0123456789ABCDEF"
402: .toCharArray();
403:
404: public String printHexBinary(byte[] data) {
405: StringBuilder r = new StringBuilder(data.length * 2);
406: for (byte b : data) {
407: r.append(hexCode[(b >> 4) & 0xF]);
408: r.append(hexCode[(b & 0xF)]);
409: }
410: return r.toString();
411: }
412:
413: public long parseUnsignedInt(String lexicalXSDUnsignedInt) {
414: return _parseLong(lexicalXSDUnsignedInt);
415: }
416:
417: public String printUnsignedInt(long val) {
418: return _printLong(val);
419: }
420:
421: public int parseUnsignedShort(String lexicalXSDUnsignedShort) {
422: return _parseInt(lexicalXSDUnsignedShort);
423: }
424:
425: public Calendar parseTime(String lexicalXSDTime) {
426: return datatypeFactory.newXMLGregorianCalendar(lexicalXSDTime)
427: .toGregorianCalendar();
428: }
429:
430: public String printTime(Calendar val) {
431: return CalendarFormatter.doFormat("%h:%m:%s%z", val);
432: }
433:
434: public Calendar parseDate(String lexicalXSDDate) {
435: return datatypeFactory.newXMLGregorianCalendar(lexicalXSDDate)
436: .toGregorianCalendar();
437: }
438:
439: public String printDate(Calendar val) {
440:
441: return CalendarFormatter
442: .doFormat((new StringBuilder("%Y-%M-%D").append("%z"))
443: .toString(), val);
444: }
445:
446: public String parseAnySimpleType(String lexicalXSDAnySimpleType) {
447: return lexicalXSDAnySimpleType;
448: // return (String)SimpleURType.theInstance._createValue( lexicalXSDAnySimpleType, null );
449: }
450:
451: public String printString(String val) {
452: // return StringType.theInstance.convertToLexicalValue( val, null );
453: return val;
454: }
455:
456: public String printInt(int val) {
457: return _printInt(val);
458: }
459:
460: public static String _printInt(int val) {
461: return String.valueOf(val);
462: }
463:
464: public String printLong(long val) {
465: return _printLong(val);
466: }
467:
468: public static String _printLong(long val) {
469: return String.valueOf(val);
470: }
471:
472: public String printDecimal(BigDecimal val) {
473: return _printDecimal(val);
474: }
475:
476: public static String _printDecimal(BigDecimal val) {
477: return val.toString();
478: }
479:
480: public String printDouble(double v) {
481: return _printDouble(v);
482: }
483:
484: public static String _printDouble(double v) {
485: if (v == Double.NaN)
486: return "NaN";
487: if (v == Double.POSITIVE_INFINITY)
488: return "INF";
489: if (v == Double.NEGATIVE_INFINITY)
490: return "-INF";
491: return String.valueOf(v);
492: }
493:
494: public String printQName(QName val, NamespaceContext nsc) {
495: return _printQName(val, nsc);
496: }
497:
498: public static String _printQName(QName val, NamespaceContext nsc) {
499: // Double-check
500: String qname;
501: String prefix = nsc.getPrefix(val.getNamespaceURI());
502: String localPart = val.getLocalPart();
503:
504: if (prefix == null || prefix.length() == 0) { // be defensive
505: qname = localPart;
506: } else {
507: qname = prefix + ':' + localPart;
508: }
509:
510: return qname;
511: }
512:
513: public String printBase64Binary(byte[] val) {
514: return _printBase64Binary(val);
515: }
516:
517: public String printUnsignedShort(int val) {
518: return String.valueOf(val);
519: }
520:
521: public String printAnySimpleType(String val) {
522: return val;
523: }
524:
525: /**
526: * Just return the string passed as a parameter but
527: * installs an instance of this class as the DatatypeConverter
528: * implementation. Used from static fixed value initializers.
529: */
530: public static String installHook(String s) {
531: DatatypeConverter.setDatatypeConverter(theInstance);
532: return s;
533: }
534:
535: // base64 decoder
536: //====================================
537:
538: private static final byte[] decodeMap = initDecodeMap();
539: private static final byte PADDING = 127;
540:
541: private static byte[] initDecodeMap() {
542: byte[] map = new byte[128];
543: int i;
544: for (i = 0; i < 128; i++)
545: map[i] = -1;
546:
547: for (i = 'A'; i <= 'Z'; i++)
548: map[i] = (byte) (i - 'A');
549: for (i = 'a'; i <= 'z'; i++)
550: map[i] = (byte) (i - 'a' + 26);
551: for (i = '0'; i <= '9'; i++)
552: map[i] = (byte) (i - '0' + 52);
553: map['+'] = 62;
554: map['/'] = 63;
555: map['='] = PADDING;
556:
557: return map;
558: }
559:
560: /**
561: * computes the length of binary data.
562: *
563: * This function also performs format check.
564: * @return -1 if format is illegal.
565: *
566: */
567: private static int calcLength(String text) {
568: final int len = text.length();
569: int base64count = 0;
570: int i;
571:
572: for (i = 0; i < len; i++) {
573: char ch = text.charAt(i);
574: if (ch == '=') // decodeMap['=']!=-1, so we have to check this first.
575: break;
576: if (ch >= 128)
577: return -1; // incorrect character
578: if (decodeMap[ch] != -1)
579: base64count++;
580: }
581:
582: return (base64count / 4) * 3
583: + Math.max(0, (base64count % 4) - 1);
584: }
585:
586: /**
587: * @param text
588: * base64Binary data is likely to be long, and decoding requires
589: * each character to be accessed twice (once for counting length, another
590: * for decoding.)
591: *
592: * A benchmark showed that taking {@link String} is faster, presumably
593: * because JIT can inline a lot of string access (with data of 1K chars, it was twice as fast)
594: */
595: public static byte[] _parseBase64Binary(String text) {
596: final int outlen = calcLength(text);
597: if (outlen == -1)
598: return null;
599: final byte[] out = new byte[outlen];
600: int o = 0;
601:
602: final int len = text.length();
603: int i;
604:
605: final byte[] quadruplet = new byte[4];
606: int q = 0;
607:
608: // convert each quadruplet to three bytes.
609: for (i = 0; i < len; i++) {
610: char ch = text.charAt(i);
611: byte v = decodeMap[ch];
612: if (v != -1)
613: quadruplet[q++] = v;
614:
615: if (q == 4) {
616: // quadruplet is now filled.
617: out[o++] = (byte) ((quadruplet[0] << 2) | (quadruplet[1] >> 4));
618: if (quadruplet[2] != PADDING)
619: out[o++] = (byte) ((quadruplet[1] << 4) | (quadruplet[2] >> 2));
620: if (quadruplet[3] != PADDING)
621: out[o++] = (byte) ((quadruplet[2] << 6) | (quadruplet[3]));
622: q = 0;
623: }
624: }
625:
626: return out;
627: }
628:
629: private static final char[] encodeMap = initEncodeMap();
630:
631: private static char[] initEncodeMap() {
632: char[] map = new char[64];
633: int i;
634: for (i = 0; i < 26; i++)
635: map[i] = (char) ('A' + i);
636: for (i = 26; i < 52; i++)
637: map[i] = (char) ('a' + (i - 26));
638: for (i = 52; i < 62; i++)
639: 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:
658: public static String _printBase64Binary(byte[] input, int offset,
659: int len) {
660: char[] buf = new char[((len + 2) / 3) * 4];
661: int ptr = _printBase64Binary(input, offset, len, buf, 0);
662: assert ptr == buf.length;
663: return new String(buf);
664: }
665:
666: /**
667: * Encodes a byte array into a char array by doing base64 encoding.
668: *
669: * The caller must supply a big enough buffer.
670: *
671: * @return
672: * the value of {@code ptr+((len+2)/3)*4}, which is the new offset
673: * in the output buffer where the further bytes should be placed.
674: */
675: public static int _printBase64Binary(byte[] input, int offset,
676: int len, char[] buf, int ptr) {
677: for (int i = offset; i < len; i += 3) {
678: switch (len - i) {
679: case 1:
680: buf[ptr++] = encode(input[i] >> 2);
681: buf[ptr++] = encode(((input[i]) & 0x3) << 4);
682: buf[ptr++] = '=';
683: buf[ptr++] = '=';
684: break;
685: case 2:
686: buf[ptr++] = encode(input[i] >> 2);
687: buf[ptr++] = encode(((input[i] & 0x3) << 4)
688: | ((input[i + 1] >> 4) & 0xF));
689: buf[ptr++] = encode((input[i + 1] & 0xF) << 2);
690: buf[ptr++] = '=';
691: break;
692: default:
693: buf[ptr++] = encode(input[i] >> 2);
694: buf[ptr++] = encode(((input[i] & 0x3) << 4)
695: | ((input[i + 1] >> 4) & 0xF));
696: buf[ptr++] = encode(((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,
716: int len, byte[] out, int ptr) {
717: byte[] buf = out;
718: int max = len + offset;
719: for (int i = offset; i < max; i += 3) {
720: switch (max - i) {
721: case 1:
722: buf[ptr++] = encodeByte(input[i] >> 2);
723: buf[ptr++] = encodeByte(((input[i]) & 0x3) << 4);
724: buf[ptr++] = '=';
725: buf[ptr++] = '=';
726: break;
727: case 2:
728: buf[ptr++] = encodeByte(input[i] >> 2);
729: buf[ptr++] = encodeByte(((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(((input[i] & 0x3) << 4)
737: | ((input[i + 1] >> 4) & 0xF));
738: buf[ptr++] = encodeByte(((input[i + 1] & 0xF) << 2)
739: | ((input[i + 2] >> 6) & 0x3));
740: buf[ptr++] = encodeByte(input[i + 2] & 0x3F);
741: break;
742: }
743: }
744:
745: return ptr;
746: }
747:
748: private static CharSequence removeOptionalPlus(CharSequence s) {
749: int len = s.length();
750:
751: if (len <= 1 || s.charAt(0) != '+')
752: return s;
753:
754: s = s.subSequence(1, len);
755: char ch = s.charAt(0);
756: if ('0' <= ch && ch <= '9')
757: return s;
758: if ('.' == ch)
759: return s;
760:
761: throw new NumberFormatException();
762: }
763:
764: private static boolean isDigitOrPeriodOrSign(char ch) {
765: if ('0' <= ch && ch <= '9')
766: return true;
767: if (ch == '+' || ch == '-' || ch == '.')
768: return true;
769: return false;
770: }
771:
772: private static final DatatypeFactory datatypeFactory;
773:
774: static {
775: try {
776: datatypeFactory = DatatypeFactory.newInstance();
777: } catch (DatatypeConfigurationException e) {
778: throw new Error(e);
779: }
780: }
781:
782: private static final class CalendarFormatter {
783: public static String doFormat(String format, Calendar cal)
784: throws IllegalArgumentException {
785: int fidx = 0;
786: int flen = format.length();
787: StringBuilder buf = new StringBuilder();
788:
789: while (fidx < flen) {
790: char fch = format.charAt(fidx++);
791:
792: if (fch != '%') { // not a meta character
793: buf.append(fch);
794: continue;
795: }
796:
797: // seen meta character. we don't do error check against the format
798: switch (format.charAt(fidx++)) {
799: case 'Y': // year
800: formatYear(cal, buf);
801: break;
802:
803: case 'M': // month
804: formatMonth(cal, buf);
805: break;
806:
807: case 'D': // days
808: formatDays(cal, buf);
809: break;
810:
811: case 'h': // hours
812: formatHours(cal, buf);
813: break;
814:
815: case 'm': // minutes
816: formatMinutes(cal, buf);
817: break;
818:
819: case 's': // parse seconds.
820: formatSeconds(cal, buf);
821: break;
822:
823: case 'z': // time zone
824: formatTimeZone(cal, buf);
825: break;
826:
827: default:
828: // illegal meta character. impossible.
829: throw new InternalError();
830: }
831: }
832:
833: return buf.toString();
834: }
835:
836: private static void formatYear(Calendar cal, StringBuilder buf) {
837: int year = cal.get(Calendar.YEAR);
838:
839: String s;
840: if (year <= 0) // negative value
841: s = Integer.toString(1 - year);
842: else
843: // positive value
844: s = Integer.toString(year);
845:
846: while (s.length() < 4)
847: s = '0' + s;
848: if (year <= 0)
849: s = '-' + s;
850:
851: buf.append(s);
852: }
853:
854: private static void formatMonth(Calendar cal, StringBuilder buf) {
855: formatTwoDigits(cal.get(Calendar.MONTH) + 1, buf);
856: }
857:
858: private static void formatDays(Calendar cal, StringBuilder buf) {
859: formatTwoDigits(cal.get(Calendar.DAY_OF_MONTH), buf);
860: }
861:
862: private static void formatHours(Calendar cal, StringBuilder buf) {
863: formatTwoDigits(cal.get(Calendar.HOUR_OF_DAY), buf);
864: }
865:
866: private static void formatMinutes(Calendar cal,
867: StringBuilder buf) {
868: formatTwoDigits(cal.get(Calendar.MINUTE), buf);
869: }
870:
871: private static void formatSeconds(Calendar cal,
872: StringBuilder buf) {
873: formatTwoDigits(cal.get(Calendar.SECOND), buf);
874: if (cal.isSet(Calendar.MILLISECOND)) { // milliseconds
875: int n = cal.get(Calendar.MILLISECOND);
876: if (n != 0) {
877: String ms = Integer.toString(n);
878: while (ms.length() < 3)
879: ms = '0' + ms; // left 0 paddings.
880:
881: buf.append('.');
882: buf.append(ms);
883: }
884: }
885: }
886:
887: /** formats time zone specifier. */
888: private static void formatTimeZone(Calendar cal,
889: StringBuilder buf) {
890: TimeZone tz = cal.getTimeZone();
891:
892: if (tz == null)
893: return;
894:
895: // otherwise print out normally.
896: int offset;
897: if (tz.inDaylightTime(cal.getTime())) {
898: offset = tz.getRawOffset()
899: + (tz.useDaylightTime() ? 3600000 : 0);
900: } else {
901: offset = tz.getRawOffset();
902: }
903:
904: if (offset >= 0)
905: buf.append('+');
906: else {
907: buf.append('-');
908: offset *= -1;
909: }
910:
911: offset /= 60 * 1000; // offset is in milli-seconds
912:
913: formatTwoDigits(offset / 60, buf);
914: buf.append(':');
915: formatTwoDigits(offset % 60, buf);
916: }
917:
918: /** formats Integer into two-character-wide string. */
919: private static final void formatTwoDigits(int n,
920: StringBuilder buf) {
921: // n is always non-negative.
922: if (n < 10)
923: buf.append('0');
924: buf.append(n);
925: }
926: }
927: }
|