src/share/jaxws_classes/javax/xml/bind/DatatypeConverterImpl.java

Thu, 31 Aug 2017 15:18:52 +0800

author
aoqi
date
Thu, 31 Aug 2017 15:18:52 +0800
changeset 637
9c07ef4934dd
parent 397
b99d7e355d4b
parent 0
373ffda63c9a
permissions
-rw-r--r--

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                 }
  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;
  1011             // otherwise print out normally.
  1012             int offset = tz.getOffset(cal.getTime().getTime());
  1014             if (offset == 0) {
  1015                 buf.append('Z');
  1016                 return;
  1019             if (offset >= 0) {
  1020                 buf.append('+');
  1021             } else {
  1022                 buf.append('-');
  1023                 offset *= -1;
  1026             offset /= 60 * 1000; // offset is in milli-seconds
  1028             formatTwoDigits(offset / 60, buf);
  1029             buf.append(':');
  1030             formatTwoDigits(offset % 60, buf);
  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');
  1039             buf.append(n);

mercurial