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