325N/A/*
325N/A * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
325N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
325N/A *
325N/A * This code is free software; you can redistribute it and/or modify it
325N/A * under the terms of the GNU General Public License version 2 only, as
325N/A * published by the Free Software Foundation. Oracle designates this
325N/A * particular file as subject to the "Classpath" exception as provided
325N/A * by Oracle in the LICENSE file that accompanied this code.
325N/A *
325N/A * This code is distributed in the hope that it will be useful, but WITHOUT
325N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
325N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
325N/A * version 2 for more details (a copy is included in the LICENSE file that
325N/A * accompanied this code).
325N/A *
325N/A * You should have received a copy of the GNU General Public License version
325N/A * 2 along with this work; if not, write to the Free Software Foundation,
325N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
325N/A *
325N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
325N/A * or visit www.oracle.com if you need additional information or have any
325N/A * questions.
325N/A */
325N/A
325N/Apackage javax.xml.bind;
325N/A
325N/Aimport java.math.BigDecimal;
325N/Aimport java.math.BigInteger;
325N/Aimport java.util.Calendar;
325N/Aimport java.util.GregorianCalendar;
325N/Aimport java.util.TimeZone;
325N/A
325N/Aimport javax.xml.namespace.QName;
325N/Aimport javax.xml.namespace.NamespaceContext;
325N/Aimport javax.xml.datatype.DatatypeFactory;
325N/Aimport javax.xml.datatype.DatatypeConfigurationException;
325N/A
325N/A/**
325N/A * This class is the JAXB RI's default implementation of the
325N/A * {@link DatatypeConverterInterface}.
325N/A *
325N/A * <p>
325N/A * When client applications specify the use of the static print/parse
325N/A * methods in {@link DatatypeConverter}, it will delegate
325N/A * to this class.
325N/A *
325N/A * <p>
325N/A * This class is responsible for whitespace normalization.
325N/A *
325N/A * @author <ul><li>Ryan Shoemaker, Sun Microsystems, Inc.</li></ul>
325N/A * @since JAXB2.1
325N/A */
325N/Afinal class DatatypeConverterImpl implements DatatypeConverterInterface {
325N/A
325N/A /**
325N/A * To avoid re-creating instances, we cache one instance.
325N/A */
325N/A public static final DatatypeConverterInterface theInstance = new DatatypeConverterImpl();
325N/A
325N/A protected DatatypeConverterImpl() {
325N/A }
325N/A
325N/A public String parseString(String lexicalXSDString) {
325N/A return lexicalXSDString;
325N/A }
325N/A
325N/A public BigInteger parseInteger(String lexicalXSDInteger) {
325N/A return _parseInteger(lexicalXSDInteger);
325N/A }
325N/A
325N/A public static BigInteger _parseInteger(CharSequence s) {
325N/A return new BigInteger(removeOptionalPlus(WhiteSpaceProcessor.trim(s)).toString());
325N/A }
325N/A
325N/A public String printInteger(BigInteger val) {
325N/A return _printInteger(val);
325N/A }
325N/A
325N/A public static String _printInteger(BigInteger val) {
325N/A return val.toString();
325N/A }
325N/A
325N/A public int parseInt(String s) {
325N/A return _parseInt(s);
325N/A }
325N/A
325N/A /**
325N/A * Faster but less robust String->int conversion.
325N/A *
325N/A * Note that:
325N/A * <ol>
325N/A * <li>XML Schema allows '+', but {@link Integer#valueOf(String)} is not.
325N/A * <li>XML Schema allows leading and trailing (but not in-between) whitespaces.
325N/A * {@link Integer#valueOf(String)} doesn't allow any.
325N/A * </ol>
325N/A */
325N/A public static int _parseInt(CharSequence s) {
325N/A int len = s.length();
325N/A int sign = 1;
325N/A
325N/A int r = 0;
325N/A
325N/A for (int i = 0; i < len; i++) {
325N/A char ch = s.charAt(i);
325N/A if (WhiteSpaceProcessor.isWhiteSpace(ch)) {
325N/A // skip whitespace
325N/A } else if ('0' <= ch && ch <= '9') {
325N/A r = r * 10 + (ch - '0');
325N/A } else if (ch == '-') {
325N/A sign = -1;
325N/A } else if (ch == '+') {
325N/A // noop
325N/A } else {
325N/A throw new NumberFormatException("Not a number: " + s);
325N/A }
325N/A }
325N/A
325N/A return r * sign;
325N/A }
325N/A
325N/A public long parseLong(String lexicalXSLong) {
325N/A return _parseLong(lexicalXSLong);
325N/A }
325N/A
325N/A public static long _parseLong(CharSequence s) {
325N/A return Long.valueOf(removeOptionalPlus(WhiteSpaceProcessor.trim(s)).toString());
325N/A }
325N/A
325N/A public short parseShort(String lexicalXSDShort) {
325N/A return _parseShort(lexicalXSDShort);
325N/A }
325N/A
325N/A public static short _parseShort(CharSequence s) {
325N/A return (short) _parseInt(s);
325N/A }
325N/A
325N/A public String printShort(short val) {
325N/A return _printShort(val);
325N/A }
325N/A
325N/A public static String _printShort(short val) {
325N/A return String.valueOf(val);
325N/A }
325N/A
325N/A public BigDecimal parseDecimal(String content) {
325N/A return _parseDecimal(content);
325N/A }
325N/A
325N/A public static BigDecimal _parseDecimal(CharSequence content) {
325N/A content = WhiteSpaceProcessor.trim(content);
325N/A
325N/A if (content.length() <= 0) {
325N/A return null;
325N/A }
325N/A
325N/A return new BigDecimal(content.toString());
325N/A
325N/A // from purely XML Schema perspective,
325N/A // this implementation has a problem, since
325N/A // in xs:decimal "1.0" and "1" is equal whereas the above
325N/A // code will return different values for those two forms.
325N/A //
325N/A // the code was originally using com.sun.msv.datatype.xsd.NumberType.load,
325N/A // but a profiling showed that the process of normalizing "1.0" into "1"
325N/A // could take non-trivial time.
325N/A //
325N/A // also, from the user's point of view, one might be surprised if
325N/A // 1 (not 1.0) is returned from "1.000"
325N/A }
325N/A
325N/A public float parseFloat(String lexicalXSDFloat) {
325N/A return _parseFloat(lexicalXSDFloat);
325N/A }
325N/A
325N/A public static float _parseFloat(CharSequence _val) {
325N/A String s = WhiteSpaceProcessor.trim(_val).toString();
325N/A /* Incompatibilities of XML Schema's float "xfloat" and Java's float "jfloat"
325N/A
325N/A * jfloat.valueOf ignores leading and trailing whitespaces,
325N/A whereas this is not allowed in xfloat.
325N/A * jfloat.valueOf allows "float type suffix" (f, F) to be
325N/A appended after float literal (e.g., 1.52e-2f), whereare
325N/A this is not the case of xfloat.
325N/A
325N/A gray zone
325N/A ---------
325N/A * jfloat allows ".523". And there is no clear statement that mentions
325N/A this case in xfloat. Although probably this is allowed.
325N/A *
325N/A */
325N/A
325N/A if (s.equals("NaN")) {
325N/A return Float.NaN;
325N/A }
325N/A if (s.equals("INF")) {
325N/A return Float.POSITIVE_INFINITY;
325N/A }
325N/A if (s.equals("-INF")) {
325N/A return Float.NEGATIVE_INFINITY;
325N/A }
325N/A
325N/A if (s.length() == 0
325N/A || !isDigitOrPeriodOrSign(s.charAt(0))
325N/A || !isDigitOrPeriodOrSign(s.charAt(s.length() - 1))) {
325N/A throw new NumberFormatException();
325N/A }
325N/A
325N/A // these screening process is necessary due to the wobble of Float.valueOf method
325N/A return Float.parseFloat(s);
325N/A }
325N/A
325N/A public String printFloat(float v) {
325N/A return _printFloat(v);
325N/A }
325N/A
325N/A public static String _printFloat(float v) {
325N/A if (Float.isNaN(v)) {
325N/A return "NaN";
325N/A }
325N/A if (v == Float.POSITIVE_INFINITY) {
325N/A return "INF";
325N/A }
325N/A if (v == Float.NEGATIVE_INFINITY) {
325N/A return "-INF";
325N/A }
325N/A return String.valueOf(v);
325N/A }
325N/A
325N/A public double parseDouble(String lexicalXSDDouble) {
325N/A return _parseDouble(lexicalXSDDouble);
325N/A }
325N/A
325N/A public static double _parseDouble(CharSequence _val) {
325N/A String val = WhiteSpaceProcessor.trim(_val).toString();
325N/A
325N/A if (val.equals("NaN")) {
325N/A return Double.NaN;
325N/A }
325N/A if (val.equals("INF")) {
325N/A return Double.POSITIVE_INFINITY;
325N/A }
325N/A if (val.equals("-INF")) {
325N/A return Double.NEGATIVE_INFINITY;
325N/A }
325N/A
325N/A if (val.length() == 0
325N/A || !isDigitOrPeriodOrSign(val.charAt(0))
325N/A || !isDigitOrPeriodOrSign(val.charAt(val.length() - 1))) {
325N/A throw new NumberFormatException(val);
325N/A }
325N/A
325N/A
325N/A // these screening process is necessary due to the wobble of Float.valueOf method
325N/A return Double.parseDouble(val);
325N/A }
325N/A
325N/A public boolean parseBoolean(String lexicalXSDBoolean) {
325N/A return _parseBoolean(lexicalXSDBoolean);
325N/A }
325N/A
325N/A public static Boolean _parseBoolean(CharSequence literal) {
325N/A if (literal == null) {
325N/A return null;
325N/A }
325N/A
325N/A int i = 0;
325N/A int len = literal.length();
325N/A char ch;
325N/A boolean value = false;
325N/A
325N/A if (literal.length() <= 0) {
325N/A return null;
325N/A }
325N/A
325N/A do {
325N/A ch = literal.charAt(i++);
325N/A } while (WhiteSpaceProcessor.isWhiteSpace(ch) && i < len);
325N/A
325N/A int strIndex = 0;
325N/A
325N/A switch (ch) {
325N/A case '1':
325N/A value = true;
325N/A break;
325N/A case '0':
325N/A value = false;
325N/A break;
325N/A case 't':
325N/A String strTrue = "rue";
325N/A do {
325N/A ch = literal.charAt(i++);
325N/A } while ((strTrue.charAt(strIndex++) == ch) && i < len && strIndex < 3);
325N/A
325N/A if (strIndex == 3) {
325N/A value = true;
325N/A } else {
325N/A return false;
325N/A }
325N/A// throw new IllegalArgumentException("String \"" + literal + "\" is not valid boolean value.");
325N/A
325N/A break;
325N/A case 'f':
325N/A String strFalse = "alse";
325N/A do {
325N/A ch = literal.charAt(i++);
325N/A } while ((strFalse.charAt(strIndex++) == ch) && i < len && strIndex < 4);
325N/A
325N/A
325N/A if (strIndex == 4) {
325N/A value = false;
325N/A } else {
325N/A return false;
325N/A }
325N/A// throw new IllegalArgumentException("String \"" + literal + "\" is not valid boolean value.");
325N/A
325N/A break;
325N/A }
325N/A
325N/A if (i < len) {
325N/A do {
325N/A ch = literal.charAt(i++);
325N/A } while (WhiteSpaceProcessor.isWhiteSpace(ch) && i < len);
325N/A }
325N/A
325N/A if (i == len) {
325N/A return value;
325N/A } else {
325N/A return null;
325N/A }
325N/A// throw new IllegalArgumentException("String \"" + literal + "\" is not valid boolean value.");
325N/A }
325N/A
325N/A public String printBoolean(boolean val) {
325N/A return val ? "true" : "false";
325N/A }
325N/A
325N/A public static String _printBoolean(boolean val) {
325N/A return val ? "true" : "false";
325N/A }
325N/A
325N/A public byte parseByte(String lexicalXSDByte) {
325N/A return _parseByte(lexicalXSDByte);
325N/A }
325N/A
325N/A public static byte _parseByte(CharSequence literal) {
325N/A return (byte) _parseInt(literal);
325N/A }
325N/A
325N/A public String printByte(byte val) {
325N/A return _printByte(val);
325N/A }
325N/A
325N/A public static String _printByte(byte val) {
325N/A return String.valueOf(val);
325N/A }
325N/A
325N/A public QName parseQName(String lexicalXSDQName, NamespaceContext nsc) {
325N/A return _parseQName(lexicalXSDQName, nsc);
325N/A }
325N/A
325N/A /**
325N/A * @return null if fails to convert.
325N/A */
325N/A public static QName _parseQName(CharSequence text, NamespaceContext nsc) {
325N/A int length = text.length();
325N/A
325N/A // trim whitespace
325N/A int start = 0;
325N/A while (start < length && WhiteSpaceProcessor.isWhiteSpace(text.charAt(start))) {
325N/A start++;
325N/A }
325N/A
325N/A int end = length;
325N/A while (end > start && WhiteSpaceProcessor.isWhiteSpace(text.charAt(end - 1))) {
325N/A end--;
325N/A }
325N/A
325N/A if (end == start) {
325N/A throw new IllegalArgumentException("input is empty");
325N/A }
325N/A
325N/A
325N/A String uri;
325N/A String localPart;
325N/A String prefix;
325N/A
325N/A // search ':'
325N/A int idx = start + 1; // no point in searching the first char. that's not valid.
325N/A while (idx < end && text.charAt(idx) != ':') {
325N/A idx++;
325N/A }
325N/A
325N/A if (idx == end) {
325N/A uri = nsc.getNamespaceURI("");
325N/A localPart = text.subSequence(start, end).toString();
325N/A prefix = "";
325N/A } else {
325N/A // Prefix exists, check everything
325N/A prefix = text.subSequence(start, idx).toString();
325N/A localPart = text.subSequence(idx + 1, end).toString();
325N/A uri = nsc.getNamespaceURI(prefix);
325N/A // uri can never be null according to javadoc,
325N/A // but some users reported that there are implementations that return null.
325N/A if (uri == null || uri.length() == 0) // crap. the NamespaceContext interface is broken.
325N/A // error: unbound prefix
325N/A {
325N/A throw new IllegalArgumentException("prefix " + prefix + " is not bound to a namespace");
325N/A }
325N/A }
325N/A
325N/A return new QName(uri, localPart, prefix);
325N/A }
325N/A
325N/A public Calendar parseDateTime(String lexicalXSDDateTime) {
325N/A return _parseDateTime(lexicalXSDDateTime);
325N/A }
325N/A
325N/A public static GregorianCalendar _parseDateTime(CharSequence s) {
325N/A String val = WhiteSpaceProcessor.trim(s).toString();
325N/A return datatypeFactory.newXMLGregorianCalendar(val).toGregorianCalendar();
325N/A }
325N/A
325N/A public String printDateTime(Calendar val) {
325N/A return _printDateTime(val);
325N/A }
325N/A
325N/A public static String _printDateTime(Calendar val) {
325N/A return CalendarFormatter.doFormat("%Y-%M-%DT%h:%m:%s%z", val);
325N/A }
325N/A
325N/A public byte[] parseBase64Binary(String lexicalXSDBase64Binary) {
325N/A return _parseBase64Binary(lexicalXSDBase64Binary);
325N/A }
325N/A
325N/A public byte[] parseHexBinary(String s) {
325N/A final int len = s.length();
325N/A
325N/A // "111" is not a valid hex encoding.
325N/A if (len % 2 != 0) {
325N/A throw new IllegalArgumentException("hexBinary needs to be even-length: " + s);
325N/A }
325N/A
325N/A byte[] out = new byte[len / 2];
325N/A
325N/A for (int i = 0; i < len; i += 2) {
325N/A int h = hexToBin(s.charAt(i));
325N/A int l = hexToBin(s.charAt(i + 1));
325N/A if (h == -1 || l == -1) {
325N/A throw new IllegalArgumentException("contains illegal character for hexBinary: " + s);
325N/A }
325N/A
325N/A out[i / 2] = (byte) (h * 16 + l);
325N/A }
325N/A
325N/A return out;
325N/A }
325N/A
325N/A private static int hexToBin(char ch) {
325N/A if ('0' <= ch && ch <= '9') {
325N/A return ch - '0';
325N/A }
325N/A if ('A' <= ch && ch <= 'F') {
325N/A return ch - 'A' + 10;
325N/A }
325N/A if ('a' <= ch && ch <= 'f') {
325N/A return ch - 'a' + 10;
325N/A }
325N/A return -1;
325N/A }
325N/A private static final char[] hexCode = "0123456789ABCDEF".toCharArray();
325N/A
325N/A public String printHexBinary(byte[] data) {
325N/A StringBuilder r = new StringBuilder(data.length * 2);
325N/A for (byte b : data) {
325N/A r.append(hexCode[(b >> 4) & 0xF]);
325N/A r.append(hexCode[(b & 0xF)]);
325N/A }
325N/A return r.toString();
325N/A }
325N/A
325N/A public long parseUnsignedInt(String lexicalXSDUnsignedInt) {
325N/A return _parseLong(lexicalXSDUnsignedInt);
325N/A }
325N/A
325N/A public String printUnsignedInt(long val) {
325N/A return _printLong(val);
325N/A }
325N/A
325N/A public int parseUnsignedShort(String lexicalXSDUnsignedShort) {
325N/A return _parseInt(lexicalXSDUnsignedShort);
325N/A }
325N/A
325N/A public Calendar parseTime(String lexicalXSDTime) {
325N/A return datatypeFactory.newXMLGregorianCalendar(lexicalXSDTime).toGregorianCalendar();
325N/A }
325N/A
325N/A public String printTime(Calendar val) {
325N/A return CalendarFormatter.doFormat("%h:%m:%s%z", val);
325N/A }
325N/A
325N/A public Calendar parseDate(String lexicalXSDDate) {
325N/A return datatypeFactory.newXMLGregorianCalendar(lexicalXSDDate).toGregorianCalendar();
325N/A }
325N/A
325N/A public String printDate(Calendar val) {
325N/A return _printDate(val);
325N/A }
325N/A
325N/A public static String _printDate(Calendar val) {
325N/A return CalendarFormatter.doFormat((new StringBuilder("%Y-%M-%D").append("%z")).toString(),val);
325N/A }
325N/A
325N/A public String parseAnySimpleType(String lexicalXSDAnySimpleType) {
325N/A return lexicalXSDAnySimpleType;
325N/A// return (String)SimpleURType.theInstance._createValue( lexicalXSDAnySimpleType, null );
325N/A }
325N/A
325N/A public String printString(String val) {
325N/A// return StringType.theInstance.convertToLexicalValue( val, null );
325N/A return val;
325N/A }
325N/A
325N/A public String printInt(int val) {
325N/A return _printInt(val);
325N/A }
325N/A
325N/A public static String _printInt(int val) {
325N/A return String.valueOf(val);
325N/A }
325N/A
325N/A public String printLong(long val) {
325N/A return _printLong(val);
325N/A }
325N/A
325N/A public static String _printLong(long val) {
325N/A return String.valueOf(val);
325N/A }
325N/A
325N/A public String printDecimal(BigDecimal val) {
325N/A return _printDecimal(val);
325N/A }
325N/A
325N/A public static String _printDecimal(BigDecimal val) {
325N/A return val.toPlainString();
325N/A }
325N/A
325N/A public String printDouble(double v) {
325N/A return _printDouble(v);
325N/A }
325N/A
325N/A public static String _printDouble(double v) {
325N/A if (Double.isNaN(v)) {
325N/A return "NaN";
325N/A }
325N/A if (v == Double.POSITIVE_INFINITY) {
325N/A return "INF";
325N/A }
325N/A if (v == Double.NEGATIVE_INFINITY) {
325N/A return "-INF";
325N/A }
325N/A return String.valueOf(v);
325N/A }
325N/A
325N/A public String printQName(QName val, NamespaceContext nsc) {
325N/A return _printQName(val, nsc);
325N/A }
325N/A
325N/A public static String _printQName(QName val, NamespaceContext nsc) {
325N/A // Double-check
325N/A String qname;
325N/A String prefix = nsc.getPrefix(val.getNamespaceURI());
325N/A String localPart = val.getLocalPart();
325N/A
325N/A if (prefix == null || prefix.length() == 0) { // be defensive
325N/A qname = localPart;
325N/A } else {
325N/A qname = prefix + ':' + localPart;
325N/A }
325N/A
325N/A return qname;
325N/A }
325N/A
325N/A public String printBase64Binary(byte[] val) {
325N/A return _printBase64Binary(val);
325N/A }
325N/A
325N/A public String printUnsignedShort(int val) {
325N/A return String.valueOf(val);
325N/A }
325N/A
325N/A public String printAnySimpleType(String val) {
325N/A return val;
325N/A }
325N/A
325N/A /**
325N/A * Just return the string passed as a parameter but
325N/A * installs an instance of this class as the DatatypeConverter
325N/A * implementation. Used from static fixed value initializers.
325N/A */
325N/A public static String installHook(String s) {
325N/A DatatypeConverter.setDatatypeConverter(theInstance);
325N/A return s;
325N/A }
325N/A// base64 decoder
325N/A private static final byte[] decodeMap = initDecodeMap();
325N/A private static final byte PADDING = 127;
325N/A
325N/A private static byte[] initDecodeMap() {
325N/A byte[] map = new byte[128];
325N/A int i;
325N/A for (i = 0; i < 128; i++) {
325N/A map[i] = -1;
325N/A }
325N/A
325N/A for (i = 'A'; i <= 'Z'; i++) {
325N/A map[i] = (byte) (i - 'A');
325N/A }
325N/A for (i = 'a'; i <= 'z'; i++) {
325N/A map[i] = (byte) (i - 'a' + 26);
325N/A }
325N/A for (i = '0'; i <= '9'; i++) {
325N/A map[i] = (byte) (i - '0' + 52);
325N/A }
325N/A map['+'] = 62;
325N/A map['/'] = 63;
325N/A map['='] = PADDING;
325N/A
325N/A return map;
325N/A }
325N/A
325N/A /**
325N/A * computes the length of binary data speculatively.
325N/A *
325N/A * <p>
325N/A * Our requirement is to create byte[] of the exact length to store the binary data.
325N/A * If we do this in a straight-forward way, it takes two passes over the data.
325N/A * Experiments show that this is a non-trivial overhead (35% or so is spent on
325N/A * the first pass in calculating the length.)
325N/A *
325N/A * <p>
325N/A * So the approach here is that we compute the length speculatively, without looking
325N/A * at the whole contents. The obtained speculative value is never less than the
325N/A * actual length of the binary data, but it may be bigger. So if the speculation
325N/A * goes wrong, we'll pay the cost of reallocation and buffer copying.
325N/A *
325N/A * <p>
325N/A * If the base64 text is tightly packed with no indentation nor illegal char
325N/A * (like what most web services produce), then the speculation of this method
325N/A * will be correct, so we get the performance benefit.
325N/A */
325N/A private static int guessLength(String text) {
325N/A final int len = text.length();
325N/A
325N/A // compute the tail '=' chars
325N/A int j = len - 1;
325N/A for (; j >= 0; j--) {
325N/A byte code = decodeMap[text.charAt(j)];
325N/A if (code == PADDING) {
325N/A continue;
325N/A }
325N/A if (code == -1) // most likely this base64 text is indented. go with the upper bound
325N/A {
325N/A return text.length() / 4 * 3;
325N/A }
325N/A break;
325N/A }
325N/A
325N/A j++; // text.charAt(j) is now at some base64 char, so +1 to make it the size
325N/A int padSize = len - j;
325N/A if (padSize > 2) // something is wrong with base64. be safe and go with the upper bound
325N/A {
325N/A return text.length() / 4 * 3;
325N/A }
325N/A
325N/A // so far this base64 looks like it's unindented tightly packed base64.
325N/A // take a chance and create an array with the expected size
325N/A return text.length() / 4 * 3 - padSize;
325N/A }
325N/A
325N/A /**
325N/A * @param text
325N/A * base64Binary data is likely to be long, and decoding requires
325N/A * each character to be accessed twice (once for counting length, another
325N/A * for decoding.)
325N/A *
325N/A * A benchmark showed that taking {@link String} is faster, presumably
325N/A * because JIT can inline a lot of string access (with data of 1K chars, it was twice as fast)
325N/A */
325N/A public static byte[] _parseBase64Binary(String text) {
325N/A final int buflen = guessLength(text);
325N/A final byte[] out = new byte[buflen];
325N/A int o = 0;
325N/A
325N/A final int len = text.length();
325N/A int i;
325N/A
325N/A final byte[] quadruplet = new byte[4];
325N/A int q = 0;
325N/A
325N/A // convert each quadruplet to three bytes.
325N/A for (i = 0; i < len; i++) {
325N/A char ch = text.charAt(i);
325N/A byte v = decodeMap[ch];
325N/A
325N/A if (v != -1) {
325N/A quadruplet[q++] = v;
325N/A }
325N/A
325N/A if (q == 4) {
325N/A // quadruplet is now filled.
325N/A out[o++] = (byte) ((quadruplet[0] << 2) | (quadruplet[1] >> 4));
325N/A if (quadruplet[2] != PADDING) {
325N/A out[o++] = (byte) ((quadruplet[1] << 4) | (quadruplet[2] >> 2));
325N/A }
325N/A if (quadruplet[3] != PADDING) {
325N/A out[o++] = (byte) ((quadruplet[2] << 6) | (quadruplet[3]));
325N/A }
325N/A q = 0;
325N/A }
325N/A }
325N/A
325N/A if (buflen == o) // speculation worked out to be OK
325N/A {
325N/A return out;
325N/A }
325N/A
325N/A // we overestimated, so need to create a new buffer
325N/A byte[] nb = new byte[o];
325N/A System.arraycopy(out, 0, nb, 0, o);
325N/A return nb;
325N/A }
325N/A private static final char[] encodeMap = initEncodeMap();
325N/A
325N/A private static char[] initEncodeMap() {
325N/A char[] map = new char[64];
325N/A int i;
325N/A for (i = 0; i < 26; i++) {
325N/A map[i] = (char) ('A' + i);
325N/A }
325N/A for (i = 26; i < 52; i++) {
325N/A map[i] = (char) ('a' + (i - 26));
325N/A }
325N/A for (i = 52; i < 62; i++) {
325N/A map[i] = (char) ('0' + (i - 52));
325N/A }
325N/A map[62] = '+';
325N/A map[63] = '/';
325N/A
325N/A return map;
325N/A }
325N/A
325N/A public static char encode(int i) {
325N/A return encodeMap[i & 0x3F];
325N/A }
325N/A
325N/A public static byte encodeByte(int i) {
325N/A return (byte) encodeMap[i & 0x3F];
325N/A }
325N/A
325N/A public static String _printBase64Binary(byte[] input) {
325N/A return _printBase64Binary(input, 0, input.length);
325N/A }
325N/A
325N/A public static String _printBase64Binary(byte[] input, int offset, int len) {
325N/A char[] buf = new char[((len + 2) / 3) * 4];
325N/A int ptr = _printBase64Binary(input, offset, len, buf, 0);
325N/A assert ptr == buf.length;
325N/A return new String(buf);
325N/A }
325N/A
325N/A /**
325N/A * Encodes a byte array into a char array by doing base64 encoding.
325N/A *
325N/A * The caller must supply a big enough buffer.
325N/A *
325N/A * @return
325N/A * the value of {@code ptr+((len+2)/3)*4}, which is the new offset
325N/A * in the output buffer where the further bytes should be placed.
325N/A */
325N/A public static int _printBase64Binary(byte[] input, int offset, int len, char[] buf, int ptr) {
325N/A // encode elements until only 1 or 2 elements are left to encode
325N/A int remaining = len;
325N/A int i;
325N/A for (i = offset;remaining >= 3; remaining -= 3, i += 3) {
325N/A buf[ptr++] = encode(input[i] >> 2);
325N/A buf[ptr++] = encode(
325N/A ((input[i] & 0x3) << 4)
325N/A | ((input[i + 1] >> 4) & 0xF));
325N/A buf[ptr++] = encode(
325N/A ((input[i + 1] & 0xF) << 2)
325N/A | ((input[i + 2] >> 6) & 0x3));
325N/A buf[ptr++] = encode(input[i + 2] & 0x3F);
325N/A }
325N/A // encode when exactly 1 element (left) to encode
325N/A if (remaining == 1) {
325N/A buf[ptr++] = encode(input[i] >> 2);
325N/A buf[ptr++] = encode(((input[i]) & 0x3) << 4);
325N/A buf[ptr++] = '=';
325N/A buf[ptr++] = '=';
325N/A }
325N/A // encode when exactly 2 elements (left) to encode
325N/A if (remaining == 2) {
325N/A buf[ptr++] = encode(input[i] >> 2);
325N/A buf[ptr++] = encode(((input[i] & 0x3) << 4)
325N/A | ((input[i + 1] >> 4) & 0xF));
325N/A buf[ptr++] = encode((input[i + 1] & 0xF) << 2);
325N/A buf[ptr++] = '=';
325N/A }
325N/A return ptr;
325N/A }
325N/A
325N/A /**
325N/A * Encodes a byte array into another byte array by first doing base64 encoding
325N/A * then encoding the result in ASCII.
325N/A *
325N/A * The caller must supply a big enough buffer.
325N/A *
325N/A * @return
325N/A * the value of {@code ptr+((len+2)/3)*4}, which is the new offset
325N/A * in the output buffer where the further bytes should be placed.
325N/A */
325N/A public static int _printBase64Binary(byte[] input, int offset, int len, byte[] out, int ptr) {
325N/A byte[] buf = out;
325N/A int remaining = len;
325N/A int i;
325N/A for (i=offset; remaining >= 3; remaining -= 3, i += 3 ) {
325N/A buf[ptr++] = encodeByte(input[i]>>2);
325N/A buf[ptr++] = encodeByte(
325N/A ((input[i]&0x3)<<4) |
325N/A ((input[i+1]>>4)&0xF));
325N/A buf[ptr++] = encodeByte(
325N/A ((input[i+1]&0xF)<<2)|
325N/A ((input[i+2]>>6)&0x3));
325N/A buf[ptr++] = encodeByte(input[i+2]&0x3F);
325N/A }
325N/A // encode when exactly 1 element (left) to encode
325N/A if (remaining == 1) {
325N/A buf[ptr++] = encodeByte(input[i]>>2);
325N/A buf[ptr++] = encodeByte(((input[i])&0x3)<<4);
325N/A buf[ptr++] = '=';
325N/A buf[ptr++] = '=';
325N/A }
325N/A // encode when exactly 2 elements (left) to encode
325N/A if (remaining == 2) {
325N/A buf[ptr++] = encodeByte(input[i]>>2);
325N/A buf[ptr++] = encodeByte(
325N/A ((input[i]&0x3)<<4) |
325N/A ((input[i+1]>>4)&0xF));
325N/A buf[ptr++] = encodeByte((input[i+1]&0xF)<<2);
325N/A buf[ptr++] = '=';
325N/A }
325N/A
325N/A return ptr;
325N/A }
325N/A
325N/A private static CharSequence removeOptionalPlus(CharSequence s) {
325N/A int len = s.length();
325N/A
325N/A if (len <= 1 || s.charAt(0) != '+') {
325N/A return s;
325N/A }
325N/A
325N/A s = s.subSequence(1, len);
325N/A char ch = s.charAt(0);
325N/A if ('0' <= ch && ch <= '9') {
325N/A return s;
325N/A }
325N/A if ('.' == ch) {
325N/A return s;
325N/A }
325N/A
325N/A throw new NumberFormatException();
325N/A }
325N/A
325N/A private static boolean isDigitOrPeriodOrSign(char ch) {
325N/A if ('0' <= ch && ch <= '9') {
325N/A return true;
325N/A }
325N/A if (ch == '+' || ch == '-' || ch == '.') {
325N/A return true;
325N/A }
325N/A return false;
325N/A }
325N/A private static final DatatypeFactory datatypeFactory;
325N/A
325N/A static {
325N/A try {
325N/A datatypeFactory = DatatypeFactory.newInstance();
325N/A } catch (DatatypeConfigurationException e) {
325N/A throw new Error(e);
325N/A }
325N/A }
325N/A
325N/A private static final class CalendarFormatter {
325N/A
325N/A public static String doFormat(String format, Calendar cal) throws IllegalArgumentException {
325N/A int fidx = 0;
325N/A int flen = format.length();
325N/A StringBuilder buf = new StringBuilder();
325N/A
325N/A while (fidx < flen) {
325N/A char fch = format.charAt(fidx++);
325N/A
325N/A if (fch != '%') { // not a meta character
325N/A buf.append(fch);
325N/A continue;
325N/A }
325N/A
325N/A // seen meta character. we don't do error check against the format
325N/A switch (format.charAt(fidx++)) {
325N/A case 'Y': // year
325N/A formatYear(cal, buf);
325N/A break;
325N/A
325N/A case 'M': // month
325N/A formatMonth(cal, buf);
325N/A break;
325N/A
325N/A case 'D': // days
325N/A formatDays(cal, buf);
325N/A break;
325N/A
325N/A case 'h': // hours
325N/A formatHours(cal, buf);
325N/A break;
325N/A
325N/A case 'm': // minutes
325N/A formatMinutes(cal, buf);
325N/A break;
325N/A
325N/A case 's': // parse seconds.
325N/A formatSeconds(cal, buf);
325N/A break;
325N/A
325N/A case 'z': // time zone
325N/A formatTimeZone(cal, buf);
325N/A break;
325N/A
325N/A default:
325N/A // illegal meta character. impossible.
325N/A throw new InternalError();
325N/A }
325N/A }
325N/A
325N/A return buf.toString();
325N/A }
325N/A
325N/A private static void formatYear(Calendar cal, StringBuilder buf) {
325N/A int year = cal.get(Calendar.YEAR);
325N/A
325N/A String s;
325N/A if (year <= 0) // negative value
325N/A {
325N/A s = Integer.toString(1 - year);
325N/A } else // positive value
325N/A {
325N/A s = Integer.toString(year);
325N/A }
325N/A
325N/A while (s.length() < 4) {
325N/A s = '0' + s;
325N/A }
325N/A if (year <= 0) {
325N/A s = '-' + s;
325N/A }
325N/A
325N/A buf.append(s);
325N/A }
325N/A
325N/A private static void formatMonth(Calendar cal, StringBuilder buf) {
325N/A formatTwoDigits(cal.get(Calendar.MONTH) + 1, buf);
325N/A }
325N/A
325N/A private static void formatDays(Calendar cal, StringBuilder buf) {
325N/A formatTwoDigits(cal.get(Calendar.DAY_OF_MONTH), buf);
325N/A }
325N/A
325N/A private static void formatHours(Calendar cal, StringBuilder buf) {
325N/A formatTwoDigits(cal.get(Calendar.HOUR_OF_DAY), buf);
325N/A }
325N/A
325N/A private static void formatMinutes(Calendar cal, StringBuilder buf) {
325N/A formatTwoDigits(cal.get(Calendar.MINUTE), buf);
325N/A }
325N/A
325N/A private static void formatSeconds(Calendar cal, StringBuilder buf) {
325N/A formatTwoDigits(cal.get(Calendar.SECOND), buf);
325N/A if (cal.isSet(Calendar.MILLISECOND)) { // milliseconds
325N/A int n = cal.get(Calendar.MILLISECOND);
325N/A if (n != 0) {
325N/A String ms = Integer.toString(n);
325N/A while (ms.length() < 3) {
325N/A ms = '0' + ms; // left 0 paddings.
325N/A }
325N/A buf.append('.');
325N/A buf.append(ms);
325N/A }
325N/A }
325N/A }
325N/A
325N/A /** formats time zone specifier. */
325N/A private static void formatTimeZone(Calendar cal, StringBuilder buf) {
325N/A TimeZone tz = cal.getTimeZone();
325N/A
325N/A if (tz == null) {
325N/A return;
325N/A }
325N/A
325N/A // otherwise print out normally.
325N/A int offset = tz.getOffset(cal.getTime().getTime());
325N/A
325N/A if (offset == 0) {
325N/A buf.append('Z');
325N/A return;
325N/A }
325N/A
325N/A if (offset >= 0) {
325N/A buf.append('+');
325N/A } else {
325N/A buf.append('-');
325N/A offset *= -1;
325N/A }
325N/A
325N/A offset /= 60 * 1000; // offset is in milli-seconds
325N/A
325N/A formatTwoDigits(offset / 60, buf);
325N/A buf.append(':');
325N/A formatTwoDigits(offset % 60, buf);
325N/A }
325N/A
325N/A /** formats Integer into two-character-wide string. */
325N/A private static void formatTwoDigits(int n, StringBuilder buf) {
325N/A // n is always non-negative.
325N/A if (n < 10) {
325N/A buf.append('0');
325N/A }
325N/A buf.append(n);
325N/A }
325N/A }
325N/A}