1 /*
   2  * Copyright (c) 2007, 2015, 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  */
  25 
  26 package javax.xml.bind;
  27 
  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;
  33 
  34 import javax.xml.namespace.QName;
  35 import javax.xml.namespace.NamespaceContext;
  36 import javax.xml.datatype.DatatypeFactory;
  37 import javax.xml.datatype.DatatypeConfigurationException;
  38 
  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 JAXB 2.1
  53  */
  54 final class DatatypeConverterImpl implements DatatypeConverterInterface {
  55 
  56     /**
  57      * To avoid re-creating instances, we cache one instance.
  58      */
  59     public static final DatatypeConverterInterface theInstance = new DatatypeConverterImpl();
  60 
  61     protected DatatypeConverterImpl() {
  62     }
  63 
  64     public String parseString(String lexicalXSDString) {
  65         return lexicalXSDString;
  66     }
  67 
  68     public BigInteger parseInteger(String lexicalXSDInteger) {
  69         return _parseInteger(lexicalXSDInteger);
  70     }
  71 
  72     public static BigInteger _parseInteger(CharSequence s) {
  73         return new BigInteger(removeOptionalPlus(WhiteSpaceProcessor.trim(s)).toString());
  74     }
  75 
  76     public String printInteger(BigInteger val) {
  77         return _printInteger(val);
  78     }
  79 
  80     public static String _printInteger(BigInteger val) {
  81         return val.toString();
  82     }
  83 
  84     public int parseInt(String s) {
  85         return _parseInt(s);
  86     }
  87 
  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;
 101 
 102         int r = 0;
 103 
 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         }
 118 
 119         return r * sign;
 120     }
 121 
 122     public long parseLong(String lexicalXSLong) {
 123         return _parseLong(lexicalXSLong);
 124     }
 125 
 126     public static long _parseLong(CharSequence s) {
 127         return Long.parseLong(removeOptionalPlus(WhiteSpaceProcessor.trim(s)).toString());
 128     }
 129 
 130     public short parseShort(String lexicalXSDShort) {
 131         return _parseShort(lexicalXSDShort);
 132     }
 133 
 134     public static short _parseShort(CharSequence s) {
 135         return (short) _parseInt(s);
 136     }
 137 
 138     public String printShort(short val) {
 139         return _printShort(val);
 140     }
 141 
 142     public static String _printShort(short val) {
 143         return String.valueOf(val);
 144     }
 145 
 146     public BigDecimal parseDecimal(String content) {
 147         return _parseDecimal(content);
 148     }
 149 
 150     public static BigDecimal _parseDecimal(CharSequence content) {
 151         content = WhiteSpaceProcessor.trim(content);
 152 
 153         if (content.length() <= 0) {
 154             return null;
 155         }
 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")) {
 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         }
 202 
 203         if (s.length() == 0
 204                 || !isDigitOrPeriodOrSign(s.charAt(0))
 205                 || !isDigitOrPeriodOrSign(s.charAt(s.length() - 1))) {
 206             throw new NumberFormatException();
 207         }
 208 
 209         // these screening process is necessary due to the wobble of Float.valueOf method
 210         return Float.parseFloat(s);
 211     }
 212 
 213     public String printFloat(float v) {
 214         return _printFloat(v);
 215     }
 216 
 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     }
 229 
 230     public double parseDouble(String lexicalXSDDouble) {
 231         return _parseDouble(lexicalXSDDouble);
 232     }
 233 
 234     public static double _parseDouble(CharSequence _val) {
 235         String val = WhiteSpaceProcessor.trim(_val).toString();
 236 
 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         }
 246 
 247         if (val.length() == 0
 248                 || !isDigitOrPeriodOrSign(val.charAt(0))
 249                 || !isDigitOrPeriodOrSign(val.charAt(val.length() - 1))) {
 250             throw new NumberFormatException(val);
 251         }
 252 
 253 
 254         // these screening process is necessary due to the wobble of Float.valueOf method
 255         return Double.parseDouble(val);
 256     }
 257 
 258     public boolean parseBoolean(String lexicalXSDBoolean) {
 259         Boolean b = _parseBoolean(lexicalXSDBoolean);
 260         return (b == null) ? false : b.booleanValue();
 261     }
 262 
 263     public static Boolean _parseBoolean(CharSequence literal) {
 264         if (literal == null) {
 265             return null;
 266         }
 267 
 268         int i = 0;
 269         int len = literal.length();
 270         char ch;
 271         boolean value = false;
 272 
 273         if (literal.length() <= 0) {
 274             return null;
 275         }
 276 
 277         do {
 278             ch = literal.charAt(i++);
 279         } while (WhiteSpaceProcessor.isWhiteSpace(ch) && i < len);
 280 
 281         int strIndex = 0;
 282 
 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);
 295 
 296                 if (strIndex == 3) {
 297                     value = true;
 298                 } else {
 299                     return false;
 300                 }
 301 //                    throw new IllegalArgumentException("String \"" + literal + "\" is not valid boolean value.");
 302 
 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);
 309 
 310 
 311                 if (strIndex == 4) {
 312                     value = false;
 313                 } else {
 314                     return false;
 315                 }
 316 //                    throw new IllegalArgumentException("String \"" + literal + "\" is not valid boolean value.");
 317 
 318                 break;
 319         }
 320 
 321         if (i < len) {
 322             do {
 323                 ch = literal.charAt(i++);
 324             } while (WhiteSpaceProcessor.isWhiteSpace(ch) && i < len);
 325         }
 326 
 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     }
 334 
 335     public String printBoolean(boolean val) {
 336         return val ? "true" : "false";
 337     }
 338 
 339     public static String _printBoolean(boolean val) {
 340         return val ? "true" : "false";
 341     }
 342 
 343     public byte parseByte(String lexicalXSDByte) {
 344         return _parseByte(lexicalXSDByte);
 345     }
 346 
 347     public static byte _parseByte(CharSequence literal) {
 348         return (byte) _parseInt(literal);
 349     }
 350 
 351     public String printByte(byte val) {
 352         return _printByte(val);
 353     }
 354 
 355     public static String _printByte(byte val) {
 356         return String.valueOf(val);
 357     }
 358 
 359     public QName parseQName(String lexicalXSDQName, NamespaceContext nsc) {
 360         return _parseQName(lexicalXSDQName, nsc);
 361     }
 362 
 363     /**
 364      * @return null if fails to convert.
 365      */
 366     public static QName _parseQName(CharSequence text, NamespaceContext nsc) {
 367         int length = text.length();
 368 
 369         // trim whitespace
 370         int start = 0;
 371         while (start < length && WhiteSpaceProcessor.isWhiteSpace(text.charAt(start))) {
 372             start++;
 373         }
 374 
 375         int end = length;
 376         while (end > start && WhiteSpaceProcessor.isWhiteSpace(text.charAt(end - 1))) {
 377             end--;
 378         }
 379 
 380         if (end == start) {
 381             throw new IllegalArgumentException("input is empty");
 382         }
 383 
 384 
 385         String uri;
 386         String localPart;
 387         String prefix;
 388 
 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         }
 394 
 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         }
 412 
 413         return new QName(uri, localPart, prefix);
 414     }
 415 
 416     public Calendar parseDateTime(String lexicalXSDDateTime) {
 417         return _parseDateTime(lexicalXSDDateTime);
 418     }
 419 
 420     public static GregorianCalendar _parseDateTime(CharSequence s) {
 421         String val = WhiteSpaceProcessor.trim(s).toString();
 422         return datatypeFactory.newXMLGregorianCalendar(val).toGregorianCalendar();
 423     }
 424 
 425     public String printDateTime(Calendar val) {
 426         return _printDateTime(val);
 427     }
 428 
 429     public static String _printDateTime(Calendar val) {
 430         return CalendarFormatter.doFormat("%Y-%M-%DT%h:%m:%s%z", val);
 431     }
 432 
 433     public byte[] parseBase64Binary(String lexicalXSDBase64Binary) {
 434         return _parseBase64Binary(lexicalXSDBase64Binary);
 435     }
 436 
 437     public byte[] parseHexBinary(String s) {
 438         final int len = s.length();
 439 
 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         }
 444 
 445         byte[] out = new byte[len / 2];
 446 
 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             }
 453 
 454             out[i / 2] = (byte) (h * 16 + l);
 455         }
 456 
 457         return out;
 458     }
 459 
 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();
 473 
 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     }
 482 
 483     public long parseUnsignedInt(String lexicalXSDUnsignedInt) {
 484         return _parseLong(lexicalXSDUnsignedInt);
 485     }
 486 
 487     public String printUnsignedInt(long val) {
 488         return _printLong(val);
 489     }
 490 
 491     public int parseUnsignedShort(String lexicalXSDUnsignedShort) {
 492         return _parseInt(lexicalXSDUnsignedShort);
 493     }
 494 
 495     public Calendar parseTime(String lexicalXSDTime) {
 496         return datatypeFactory.newXMLGregorianCalendar(lexicalXSDTime).toGregorianCalendar();
 497     }
 498 
 499     public String printTime(Calendar val) {
 500         return CalendarFormatter.doFormat("%h:%m:%s%z", val);
 501     }
 502 
 503     public Calendar parseDate(String lexicalXSDDate) {
 504         return datatypeFactory.newXMLGregorianCalendar(lexicalXSDDate).toGregorianCalendar();
 505     }
 506 
 507     public String printDate(Calendar val) {
 508         return _printDate(val);
 509     }
 510 
 511     public static String _printDate(Calendar val) {
 512         return CalendarFormatter.doFormat((new StringBuilder("%Y-%M-%D").append("%z")).toString(),val);
 513     }
 514 
 515     public String parseAnySimpleType(String lexicalXSDAnySimpleType) {
 516         return lexicalXSDAnySimpleType;
 517 //        return (String)SimpleURType.theInstance._createValue( lexicalXSDAnySimpleType, null );
 518     }
 519 
 520     public String printString(String val) {
 521 //        return StringType.theInstance.convertToLexicalValue( val, null );
 522         return val;
 523     }
 524 
 525     public String printInt(int val) {
 526         return _printInt(val);
 527     }
 528 
 529     public static String _printInt(int val) {
 530         return String.valueOf(val);
 531     }
 532 
 533     public String printLong(long val) {
 534         return _printLong(val);
 535     }
 536 
 537     public static String _printLong(long val) {
 538         return String.valueOf(val);
 539     }
 540 
 541     public String printDecimal(BigDecimal val) {
 542         return _printDecimal(val);
 543     }
 544 
 545     public static String _printDecimal(BigDecimal val) {
 546         return val.toPlainString();
 547     }
 548 
 549     public String printDouble(double v) {
 550         return _printDouble(v);
 551     }
 552 
 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     }
 565 
 566     public String printQName(QName val, NamespaceContext nsc) {
 567         return _printQName(val, nsc);
 568     }
 569 
 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();
 575 
 576         if (prefix == null || prefix.length() == 0) { // be defensive
 577             qname = localPart;
 578         } else {
 579             qname = prefix + ':' + localPart;
 580         }
 581 
 582         return qname;
 583     }
 584 
 585     public String printBase64Binary(byte[] val) {
 586         return _printBase64Binary(val);
 587     }
 588 
 589     public String printUnsignedShort(int val) {
 590         return String.valueOf(val);
 591     }
 592 
 593     public String printAnySimpleType(String val) {
 594         return val;
 595     }
 596 
 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;
 609 
 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         }
 616 
 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;
 629 
 630         return map;
 631     }
 632 
 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();
 655 
 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         }
 669 
 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         }
 676 
 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     }
 681 
 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;
 695 
 696         final int len = text.length();
 697         int i;
 698 
 699         final byte[] quadruplet = new byte[4];
 700         int q = 0;
 701 
 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];
 706 
 707             if (v != -1) {
 708                 quadruplet[q++] = v;
 709             }
 710 
 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         }
 723 
 724         if (buflen == o) // speculation worked out to be OK
 725         {
 726             return out;
 727         }
 728 
 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();
 735 
 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] = '/';
 750 
 751         return map;
 752     }
 753 
 754     public static char encode(int i) {
 755         return encodeMap[i & 0x3F];
 756     }
 757 
 758     public static byte encodeByte(int i) {
 759         return (byte) encodeMap[i & 0x3F];
 760     }
 761 
 762     public static String _printBase64Binary(byte[] input) {
 763         return _printBase64Binary(input, 0, input.length);
 764     }
 765 
 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     }
 772 
 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     }
 813 
 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         }
 854 
 855         return ptr;
 856     }
 857 
 858     private static CharSequence removeOptionalPlus(CharSequence s) {
 859         int len = s.length();
 860 
 861         if (len <= 1 || s.charAt(0) != '+') {
 862             return s;
 863         }
 864 
 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         }
 873 
 874         throw new NumberFormatException();
 875     }
 876 
 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;
 887 
 888     static {
 889         try {
 890             datatypeFactory = DatatypeFactory.newInstance();
 891         } catch (DatatypeConfigurationException e) {
 892             throw new Error(e);
 893         }
 894     }
 895 
 896     private static final class CalendarFormatter {
 897 
 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();
 902 
 903             while (fidx < flen) {
 904                 char fch = format.charAt(fidx++);
 905 
 906                 if (fch != '%') {  // not a meta character
 907                     buf.append(fch);
 908                     continue;
 909                 }
 910 
 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;
 916 
 917                     case 'M': // month
 918                         formatMonth(cal, buf);
 919                         break;
 920 
 921                     case 'D': // days
 922                         formatDays(cal, buf);
 923                         break;
 924 
 925                     case 'h': // hours
 926                         formatHours(cal, buf);
 927                         break;
 928 
 929                     case 'm': // minutes
 930                         formatMinutes(cal, buf);
 931                         break;
 932 
 933                     case 's': // parse seconds.
 934                         formatSeconds(cal, buf);
 935                         break;
 936 
 937                     case 'z': // time zone
 938                         formatTimeZone(cal, buf);
 939                         break;
 940 
 941                     default:
 942                         // illegal meta character. impossible.
 943                         throw new InternalError();
 944                 }
 945             }
 946 
 947             return buf.toString();
 948         }
 949 
 950         private static void formatYear(Calendar cal, StringBuilder buf) {
 951             int year = cal.get(Calendar.YEAR);
 952 
 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             }
 961 
 962             while (s.length() < 4) {
 963                 s = '0' + s;
 964             }
 965             if (year <= 0) {
 966                 s = '-' + s;
 967             }
 968 
 969             buf.append(s);
 970         }
 971 
 972         private static void formatMonth(Calendar cal, StringBuilder buf) {
 973             formatTwoDigits(cal.get(Calendar.MONTH) + 1, buf);
 974         }
 975 
 976         private static void formatDays(Calendar cal, StringBuilder buf) {
 977             formatTwoDigits(cal.get(Calendar.DAY_OF_MONTH), buf);
 978         }
 979 
 980         private static void formatHours(Calendar cal, StringBuilder buf) {
 981             formatTwoDigits(cal.get(Calendar.HOUR_OF_DAY), buf);
 982         }
 983 
 984         private static void formatMinutes(Calendar cal, StringBuilder buf) {
 985             formatTwoDigits(cal.get(Calendar.MINUTE), buf);
 986         }
 987 
 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         }
1002 
1003         /** formats time zone specifier. */
1004         private static void formatTimeZone(Calendar cal, StringBuilder buf) {
1005             TimeZone tz = cal.getTimeZone();
1006 
1007             if (tz == null) {
1008                 return;
1009             }
1010 
1011             // otherwise print out normally.
1012             int offset = tz.getOffset(cal.getTime().getTime());
1013 
1014             if (offset == 0) {
1015                 buf.append('Z');
1016                 return;
1017             }
1018 
1019             if (offset >= 0) {
1020                 buf.append('+');
1021             } else {
1022                 buf.append('-');
1023                 offset *= -1;
1024             }
1025 
1026             offset /= 60 * 1000; // offset is in milli-seconds
1027 
1028             formatTwoDigits(offset / 60, buf);
1029             buf.append(':');
1030             formatTwoDigits(offset % 60, buf);
1031         }
1032 
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 }
--- EOF ---