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