0001: /*
0002: Copyright (c) 2002-2005, Dennis M. Sosnoski.
0003: All rights reserved.
0004:
0005: Redistribution and use in source and binary forms, with or without modification,
0006: are permitted provided that the following conditions are met:
0007:
0008: * Redistributions of source code must retain the above copyright notice, this
0009: list of conditions and the following disclaimer.
0010: * Redistributions in binary form must reproduce the above copyright notice,
0011: this list of conditions and the following disclaimer in the documentation
0012: and/or other materials provided with the distribution.
0013: * Neither the name of JiBX nor the names of its contributors may be used
0014: to endorse or promote products derived from this software without specific
0015: prior written permission.
0016:
0017: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
0018: ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
0019: WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0020: DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
0021: ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
0022: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
0023: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
0024: ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0025: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0026: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0027: */
0028:
0029: package org.jibx.runtime;
0030:
0031: import java.lang.reflect.Array; //#!j2me{
0032: import java.sql.Time;
0033: import java.sql.Timestamp; //#j2me}
0034: import java.util.ArrayList;
0035: import java.util.Calendar;
0036: import java.util.Date;
0037: import java.util.GregorianCalendar;
0038: import java.util.List;
0039:
0040: /**
0041: * Utility class supplying static methods. Date serialization is based on the
0042: * algorithms published by Peter Baum (http://www.capecod.net/~pbaum). All date
0043: * handling is done according to the W3C Schema specification, which uses a
0044: * proleptic Gregorian calendar with no year 0. Note that this differs from the
0045: * Java date handling, which uses a discontinuous Gregorian calendar.
0046: *
0047: * @author Dennis M. Sosnoski
0048: * @version 1.0
0049: */
0050: public abstract class Utility {
0051: /** Minimum size for array returned by {@link #growArray}. */
0052: public static final int MINIMUM_GROWN_ARRAY_SIZE = 16;
0053:
0054: /** Number of milliseconds in a minute. */
0055: private static final int MSPERMINUTE = 60000;
0056:
0057: /** Number of milliseconds in an hour. */
0058: private static final int MSPERHOUR = MSPERMINUTE * 60;
0059:
0060: /** Number of milliseconds in a day. */
0061: private static final int MSPERDAY = MSPERHOUR * 24;
0062:
0063: /** Number of milliseconds in a day as a long. */
0064: private static final long LMSPERDAY = (long) MSPERDAY;
0065:
0066: /** Number of milliseconds in a (non-leap) year. */
0067: private static final long MSPERYEAR = LMSPERDAY * 365;
0068:
0069: /** Average number of milliseconds in a year within century. */
0070: private static final long MSPERAVGYEAR = (long) (MSPERDAY * 365.25);
0071:
0072: /** Number of milliseconds in a normal century. */
0073: private static final long MSPERCENTURY = (long) (MSPERDAY * 36524.25);
0074:
0075: /** Millisecond value of base time for internal representation. This gives
0076: the bias relative to January 1 of the year 1 C.E. */
0077: private static final long TIME_BASE = 1969 * MSPERYEAR
0078: + (1969 / 4 - 19 + 4) * LMSPERDAY;
0079:
0080: /** Day number for start of month in non-leap year. */
0081: private static final int[] MONTHS_NONLEAP = { 0, 31, 59, 90, 120,
0082: 151, 181, 212, 243, 273, 304, 334, 365 };
0083:
0084: /** Day number for start of month in non-leap year. */
0085: private static final int[] MONTHS_LEAP = { 0, 31, 60, 91, 121, 152,
0086: 182, 213, 244, 274, 305, 335, 366 };
0087:
0088: /** Millisecond count prior to start of month in March 1-biased year. */
0089: private static final long[] BIAS_MONTHMS = { 0 * LMSPERDAY,
0090: 0 * LMSPERDAY, 0 * LMSPERDAY, 0 * LMSPERDAY,
0091: 31 * LMSPERDAY, 61 * LMSPERDAY, 92 * LMSPERDAY,
0092: 122 * LMSPERDAY, 153 * LMSPERDAY, 184 * LMSPERDAY,
0093: 214 * LMSPERDAY, 245 * LMSPERDAY, 275 * LMSPERDAY,
0094: 306 * LMSPERDAY, 337 * LMSPERDAY };
0095:
0096: /** Date for setting change to Gregorian calendar. */
0097: private static Date BEGINNING_OF_TIME = new Date(Long.MIN_VALUE);
0098:
0099: /** Pad character for base64 encoding. */
0100: private static final char PAD_CHAR = '=';
0101:
0102: /** Characters used in base64 encoding. */
0103: private static final char[] s_base64Chars = { 'A', 'B', 'C', 'D',
0104: 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
0105: 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
0106: 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
0107: 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
0108: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };
0109:
0110: /** Values corresponding to characters used in bas64 encoding. */
0111: private static final byte[] s_base64Values = new byte[128];
0112: static {
0113: for (int i = 0; i < s_base64Values.length; i++) {
0114: s_base64Values[i] = -1;
0115: }
0116: s_base64Values[PAD_CHAR] = 0;
0117: for (int i = 0; i < s_base64Chars.length; i++) {
0118: s_base64Values[s_base64Chars[i]] = (byte) i;
0119: }
0120: }
0121:
0122: /**
0123: * Parse digits in text as integer value. This internal method is used
0124: * for number values embedded within lexical structures. Only decimal
0125: * digits can be included in the text range parsed.
0126: *
0127: * @param text text to be parsed
0128: * @param offset starting offset in text
0129: * @param length number of digits to be parsed
0130: * @return converted positive integer value
0131: * @throws JiBXException on parse error
0132: */
0133: private static int parseDigits(String text, int offset, int length)
0134: throws JiBXException {
0135:
0136: // check if overflow a potential problem
0137: int value = 0;
0138: if (length > 9) {
0139:
0140: // use library parse code for potential overflow
0141: try {
0142: value = Integer.parseInt(text.substring(offset, offset
0143: + length));
0144: } catch (NumberFormatException ex) {
0145: throw new JiBXException(ex.getMessage());
0146: }
0147:
0148: } else {
0149:
0150: // parse with no overflow worries
0151: int limit = offset + length;
0152: while (offset < limit) {
0153: char chr = text.charAt(offset++);
0154: if (chr >= '0' && chr <= '9') {
0155: value = value * 10 + (chr - '0');
0156: } else {
0157: throw new JiBXException("Non-digit in number value");
0158: }
0159: }
0160:
0161: }
0162: return value;
0163: }
0164:
0165: /**
0166: * Parse integer value from text. Integer values are parsed with optional
0167: * leading sign flag, followed by any number of digits.
0168: *
0169: * @param text text to be parsed
0170: * @return converted integer value
0171: * @throws JiBXException on parse error
0172: */
0173: public static int parseInt(String text) throws JiBXException {
0174:
0175: // make sure there's text to be processed
0176: text = text.trim();
0177: int offset = 0;
0178: int limit = text.length();
0179: if (limit == 0) {
0180: throw new JiBXException("Empty number value");
0181: }
0182:
0183: // check leading sign present in text
0184: boolean negate = false;
0185: char chr = text.charAt(0);
0186: if (chr == '-') {
0187: if (limit > 9) {
0188:
0189: // special case to make sure maximum negative value handled
0190: try {
0191: return Integer.parseInt(text);
0192: } catch (NumberFormatException ex) {
0193: throw new JiBXException(ex.getMessage());
0194: }
0195:
0196: } else {
0197: negate = true;
0198: offset++;
0199: }
0200: } else if (chr == '+') {
0201: offset++;
0202: }
0203: if (offset >= limit) {
0204: throw new JiBXException("Invalid number format");
0205: }
0206:
0207: // handle actual value conversion
0208: int value = parseDigits(text, offset, limit - offset);
0209: if (negate) {
0210: return -value;
0211: } else {
0212: return value;
0213: }
0214: }
0215:
0216: /**
0217: * Serialize int value to text.
0218: *
0219: * @param value int value to be serialized
0220: * @return text representation of value
0221: */
0222: public static String serializeInt(int value) {
0223: return Integer.toString(value);
0224: }
0225:
0226: /**
0227: * Parse long value from text. Long values are parsed with optional
0228: * leading sign flag, followed by any number of digits.
0229: *
0230: * @param text text to be parsed
0231: * @return converted long value
0232: * @throws JiBXException on parse error
0233: */
0234: public static long parseLong(String text) throws JiBXException {
0235:
0236: // make sure there's text to be processed
0237: text = text.trim();
0238: int offset = 0;
0239: int limit = text.length();
0240: if (limit == 0) {
0241: throw new JiBXException("Empty number value");
0242: }
0243:
0244: // check leading sign present in text
0245: boolean negate = false;
0246: char chr = text.charAt(0);
0247: if (chr == '-') {
0248: negate = true;
0249: offset++;
0250: } else if (chr == '+') {
0251: offset++;
0252: }
0253: if (offset >= limit) {
0254: throw new JiBXException("Invalid number format");
0255: }
0256:
0257: // check if overflow a potential problem
0258: long value = 0;
0259: if (limit - offset > 18) {
0260:
0261: // pass text to library parse code (less leading +)
0262: if (chr == '+') {
0263: text = text.substring(1);
0264: }
0265: try {
0266: value = Long.parseLong(text);
0267: } catch (NumberFormatException ex) {
0268: throw new JiBXException(ex.getMessage());
0269: }
0270:
0271: } else {
0272:
0273: // parse with no overflow worries
0274: while (offset < limit) {
0275: chr = text.charAt(offset++);
0276: if (chr >= '0' && chr <= '9') {
0277: value = value * 10 + (chr - '0');
0278: } else {
0279: throw new JiBXException("Non-digit in number value");
0280: }
0281: }
0282: if (negate) {
0283: value = -value;
0284: }
0285:
0286: }
0287: return value;
0288: }
0289:
0290: /**
0291: * Serialize long value to text.
0292: *
0293: * @param value long value to be serialized
0294: * @return text representation of value
0295: */
0296: public static String serializeLong(long value) {
0297: return Long.toString(value);
0298: }
0299:
0300: /**
0301: * Convert gYear text to Java date. Date values are expected to be in
0302: * W3C XML Schema standard format as CCYY, with optional leading sign.
0303: *
0304: * @param text text to be parsed
0305: * @return start of year date as millisecond value from 1 C.E.
0306: * @throws JiBXException on parse error
0307: */
0308: public static long parseYear(String text) throws JiBXException {
0309:
0310: // start by validating the length
0311: text = text.trim();
0312: boolean valid = true;
0313: int minc = 4;
0314: char chr = text.charAt(0);
0315: if (chr == '-') {
0316: minc = 5;
0317: } else if (chr == '+') {
0318: valid = false;
0319: }
0320: if (text.length() < minc) {
0321: valid = false;
0322: }
0323: if (!valid) {
0324: throw new JiBXException("Invalid year format");
0325: }
0326:
0327: // handle year conversion
0328: int year = parseInt(text);
0329: if (year == 0) {
0330: throw new JiBXException("Year value 0 is not allowed");
0331: }
0332: if (year > 0) {
0333: year--;
0334: }
0335: long day = ((long) year) * 365 + year / 4 - year / 100 + year
0336: / 400;
0337: return day * MSPERDAY - TIME_BASE;
0338: }
0339:
0340: /**
0341: * Parse short value from text. Short values are parsed with optional
0342: * leading sign flag, followed by any number of digits.
0343: *
0344: * @param text text to be parsed
0345: * @return converted short value
0346: * @throws JiBXException on parse error
0347: */
0348: public static short parseShort(String text) throws JiBXException {
0349: int value = parseInt(text);
0350: if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
0351: throw new JiBXException("Value out of range");
0352: }
0353: return (short) value;
0354: }
0355:
0356: /**
0357: * Serialize short value to text.
0358: *
0359: * @param value short value to be serialized
0360: * @return text representation of value
0361: */
0362: public static String serializeShort(short value) {
0363: return Short.toString(value);
0364: }
0365:
0366: /**
0367: * Parse byte value from text. Byte values are parsed with optional
0368: * leading sign flag, followed by any number of digits.
0369: *
0370: * @param text text to be parsed
0371: * @return converted byte value
0372: * @throws JiBXException on parse error
0373: */
0374: public static byte parseByte(String text) throws JiBXException {
0375: int value = parseInt(text);
0376: if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
0377: throw new JiBXException("Value out of range");
0378: }
0379: return (byte) value;
0380: }
0381:
0382: /**
0383: * Serialize byte value to text.
0384: *
0385: * @param value byte value to be serialized
0386: * @return text representation of value
0387: */
0388: public static String serializeByte(byte value) {
0389: return Byte.toString(value);
0390: }
0391:
0392: /**
0393: * Parse boolean value from text. Boolean values are parsed as either text
0394: * "true" and "false", or "1" and "0" numeric equivalents.
0395: *
0396: * @param text text to be parsed
0397: * @return converted boolean value
0398: * @throws JiBXException on parse error
0399: */
0400: public static boolean parseBoolean(String text)
0401: throws JiBXException {
0402: text = text.trim();
0403: if ("true".equals(text) || "1".equals(text)) {
0404: return true;
0405: } else if ("false".equals(text) || "0".equals(text)) {
0406: return false;
0407: } else {
0408: throw new JiBXException("Invalid boolean value");
0409: }
0410: }
0411:
0412: /**
0413: * Serialize boolean value to text. This serializes the value using the
0414: * text representation as "true" or "false".
0415: *
0416: * @param value boolean value to be serialized
0417: * @return text representation of value
0418: */
0419: public static String serializeBoolean(boolean value) {
0420: return value ? "true" : "false";
0421: }
0422:
0423: /**
0424: * Parse char value from text as unsigned 16-bit integer. Char values are
0425: * parsed with optional leading sign flag, followed by any number of digits.
0426: *
0427: * @param text text to be parsed
0428: * @return converted char value
0429: * @throws JiBXException on parse error
0430: */
0431: public static char parseChar(String text) throws JiBXException {
0432: int value = parseInt(text);
0433: if (value < Character.MIN_VALUE || value > Character.MAX_VALUE) {
0434: throw new JiBXException("Value out of range");
0435: }
0436: return (char) value;
0437: }
0438:
0439: /**
0440: * Serialize char value to text as unsigned 16-bit integer.
0441: *
0442: * @param value char value to be serialized
0443: * @return text representation of value
0444: */
0445: public static String serializeChar(char value) {
0446: return Integer.toString(value);
0447: }
0448:
0449: /**
0450: * Parse char value from text as character value. This requires that the
0451: * string must be of length one.
0452: *
0453: * @param text text to be parsed
0454: * @return converted char value
0455: * @throws JiBXException on parse error
0456: */
0457: public static char parseCharString(String text)
0458: throws JiBXException {
0459: if (text.length() == 1) {
0460: return text.charAt(0);
0461: } else {
0462: throw new JiBXException("Input must be a single character");
0463: }
0464: }
0465:
0466: /**
0467: * Deserialize char value from text as character value. This requires that
0468: * the string must be null or of length one.
0469: *
0470: * @param text text to be parsed (may be <code>null</code>)
0471: * @return converted char value
0472: * @throws JiBXException on parse error
0473: */
0474: public static char deserializeCharString(String text)
0475: throws JiBXException {
0476: if (text == null) {
0477: return 0;
0478: } else {
0479: return parseCharString(text);
0480: }
0481: }
0482:
0483: /**
0484: * Serialize char value to text as string of length one.
0485: *
0486: * @param value char value to be serialized
0487: * @return text representation of value
0488: */
0489: public static String serializeCharString(char value) {
0490: return String.valueOf(value);
0491: }
0492:
0493: /**
0494: * Parse float value from text. This uses the W3C XML Schema format for
0495: * floats, with the exception that it will accept "+NaN" and "-NaN" as
0496: * valid formats. This is not in strict compliance with the specification,
0497: * but is included for interoperability with other Java XML processing.
0498: *
0499: * @param text text to be parsed
0500: * @return converted float value
0501: * @throws JiBXException on parse error
0502: */
0503: public static float parseFloat(String text) throws JiBXException {
0504: text = text.trim();
0505: if ("-INF".equals(text)) {
0506: return Float.NEGATIVE_INFINITY;
0507: } else if ("INF".equals(text)) {
0508: return Float.POSITIVE_INFINITY;
0509: } else {
0510: try {
0511: return Float.parseFloat(text);
0512: } catch (NumberFormatException ex) {
0513: throw new JiBXException(ex.getMessage());
0514: }
0515: }
0516: }
0517:
0518: /**
0519: * Serialize float value to text.
0520: *
0521: * @param value float value to be serialized
0522: * @return text representation of value
0523: */
0524: public static String serializeFloat(float value) {
0525: if (Float.isInfinite(value)) {
0526: return (value < 0.0f) ? "-INF" : "INF";
0527: } else {
0528: return Float.toString(value);
0529: }
0530: }
0531:
0532: /**
0533: * Parse double value from text. This uses the W3C XML Schema format for
0534: * doubles, with the exception that it will accept "+NaN" and "-NaN" as
0535: * valid formats. This is not in strict compliance with the specification,
0536: * but is included for interoperability with other Java XML processing.
0537: *
0538: * @param text text to be parsed
0539: * @return converted double value
0540: * @throws JiBXException on parse error
0541: */
0542: public static double parseDouble(String text) throws JiBXException {
0543: text = text.trim();
0544: if ("-INF".equals(text)) {
0545: return Double.NEGATIVE_INFINITY;
0546: } else if ("INF".equals(text)) {
0547: return Double.POSITIVE_INFINITY;
0548: } else {
0549: try {
0550: return Double.parseDouble(text);
0551: } catch (NumberFormatException ex) {
0552: throw new JiBXException(ex.getMessage());
0553: }
0554: }
0555: }
0556:
0557: /**
0558: * Serialize double value to text.
0559: *
0560: * @param value double value to be serialized
0561: * @return text representation of value
0562: */
0563: public static String serializeDouble(double value) {
0564: if (Double.isInfinite(value)) {
0565: return (value < 0.0f) ? "-INF" : "INF";
0566: } else {
0567: return Double.toString(value);
0568: }
0569: }
0570:
0571: /**
0572: * Convert gYearMonth text to Java date. Date values are expected to be in
0573: * W3C XML Schema standard format as CCYY-MM, with optional
0574: * leading sign.
0575: *
0576: * @param text text to be parsed
0577: * @return start of month in year date as millisecond value
0578: * @throws JiBXException on parse error
0579: */
0580: public static long parseYearMonth(String text) throws JiBXException {
0581:
0582: // start by validating the length and basic format
0583: text = text.trim();
0584: boolean valid = true;
0585: int minc = 7;
0586: char chr = text.charAt(0);
0587: if (chr == '-') {
0588: minc = 8;
0589: } else if (chr == '+') {
0590: valid = false;
0591: }
0592: int split = text.length() - 3;
0593: if (text.length() < minc) {
0594: valid = false;
0595: } else {
0596: if (text.charAt(split) != '-') {
0597: valid = false;
0598: }
0599: }
0600: if (!valid) {
0601: throw new JiBXException("Invalid date format");
0602: }
0603:
0604: // handle year and month conversion
0605: int year = parseInt(text.substring(0, split));
0606: if (year == 0) {
0607: throw new JiBXException("Year value 0 is not allowed");
0608: }
0609: int month = parseDigits(text, split + 1, 2) - 1;
0610: if (month < 0 || month > 11) {
0611: throw new JiBXException("Month value out of range");
0612: }
0613: boolean leap = (year % 4 == 0)
0614: && !((year % 100 == 0) && (year % 400 != 0));
0615: if (year > 0) {
0616: year--;
0617: }
0618: long day = ((long) year) * 365 + year / 4 - year / 100 + year
0619: / 400 + (leap ? MONTHS_LEAP : MONTHS_NONLEAP)[month];
0620: return day * MSPERDAY - TIME_BASE;
0621: }
0622:
0623: /**
0624: * Convert date text to Java date. Date values are expected to be in
0625: * W3C XML Schema standard format as CCYY-MM-DD, with optional
0626: * leading sign and trailing time zone (though the time zone is ignored
0627: * in this case).
0628: *
0629: * @param text text to be parsed
0630: * @return start of day in month and year date as millisecond value
0631: * @throws JiBXException on parse error
0632: */
0633: public static long parseDate(String text) throws JiBXException {
0634:
0635: // start by validating the length and basic format
0636: int split = validateDate(text);
0637:
0638: // handle year, month, and day conversion
0639: int year = parseInt(text.substring(0, split));
0640: if (year == 0) {
0641: throw new JiBXException("Year value 0 is not allowed");
0642: }
0643: int month = parseDigits(text, split + 1, 2) - 1;
0644: if (month < 0 || month > 11) {
0645: throw new JiBXException("Month value out of range");
0646: }
0647: long day = parseDigits(text, split + 4, 2) - 1;
0648: boolean leap = (year % 4 == 0)
0649: && !((year % 100 == 0) && (year % 400 != 0));
0650: int[] starts = leap ? MONTHS_LEAP : MONTHS_NONLEAP;
0651: if (day < 0 || day >= (starts[month + 1] - starts[month])) {
0652: throw new JiBXException("Day value out of range");
0653: }
0654: if (year > 0) {
0655: year--;
0656: }
0657: day += ((long) year) * 365 + year / 4 - year / 100 + year / 400
0658: + starts[month];
0659: return day * MSPERDAY - TIME_BASE;
0660: }
0661:
0662: /**
0663: * Deserialize date from text. Date values are expected to match W3C XML
0664: * Schema standard format as CCYY-MM-DD, with optional leading minus sign
0665: * if necessary. This method follows standard JiBX deserializer usage
0666: * requirements by accepting a <code>null</code> input.
0667: *
0668: * @param text text to be parsed (may be <code>null</code>)
0669: * @return converted date, or <code>null</code> if passed <code>null</code>
0670: * input
0671: * @throws JiBXException on parse error
0672: */
0673: public static Date deserializeDate(String text)
0674: throws JiBXException {
0675: if (text == null) {
0676: return null;
0677: } else {
0678: return new Date(parseDate(text));
0679: }
0680: }
0681:
0682: /**
0683: * Validate a date text string.
0684: *
0685: * @param text
0686: * @return offset past end of year in text
0687: * @throws JiBXException on validation error
0688: */
0689: private static int validateDate(String text) throws JiBXException {
0690:
0691: // start by validating the length and basic format
0692: boolean valid = true;
0693: int minc = 10;
0694: char chr = text.charAt(0);
0695: if (chr == '-') {
0696: minc = 11;
0697: } else if (chr == '+') {
0698: valid = false;
0699: }
0700: int split = text.length() - 6;
0701: if (text.length() < minc) {
0702: valid = false;
0703: } else {
0704: if (text.charAt(split) != '-'
0705: || text.charAt(split + 3) != '-') {
0706: valid = false;
0707: }
0708: }
0709: if (!valid) {
0710: throw new JiBXException("Invalid date format");
0711: }
0712: return split;
0713: }
0714:
0715: //#!j2me{
0716: /**
0717: * Deserialize SQL date from text. Date values are expected to match W3C XML
0718: * Schema standard format as CCYY-MM-DD, with optional leading minus sign
0719: * if necessary. This method follows standard JiBX deserializer usage
0720: * requirements by accepting a <code>null</code> input.
0721: *
0722: * @param text text to be parsed (may be <code>null</code>)
0723: * @return converted date, or <code>null</code> if passed <code>null</code>
0724: * input
0725: * @throws JiBXException on parse error
0726: */
0727: public static java.sql.Date deserializeSqlDate(String text)
0728: throws JiBXException {
0729: if (text == null) {
0730: return null;
0731: } else {
0732:
0733: // make sure date is avlid
0734: int split = validateDate(text);
0735:
0736: // handle year, month, and day conversion
0737: int year = parseInt(text.substring(0, split));
0738: if (year == 0) {
0739: throw new JiBXException("Year value 0 is not allowed");
0740: }
0741: int month = parseDigits(text, split + 1, 2) - 1;
0742: if (month < 0 || month > 11) {
0743: throw new JiBXException("Month value out of range");
0744: }
0745: int day = parseDigits(text, split + 4, 2) - 1;
0746: boolean leap = (year % 4 == 0)
0747: && !((year % 100 == 0) && (year % 400 != 0));
0748: int[] starts = leap ? MONTHS_LEAP : MONTHS_NONLEAP;
0749: if (day < 0 || day >= (starts[month + 1] - starts[month])) {
0750: throw new JiBXException("Day value out of range");
0751: }
0752: if (year < 0) {
0753: year++;
0754: }
0755:
0756: // set it into a calendar
0757: GregorianCalendar cal;
0758: if (year < 1800) {
0759: cal = new GregorianCalendar();
0760: cal.setGregorianChange(BEGINNING_OF_TIME);
0761: cal.clear();
0762: cal.set(year, month, day + 1);
0763: } else {
0764: cal = new GregorianCalendar(year, month, day + 1);
0765: }
0766: return new java.sql.Date(cal.getTime().getTime());
0767: }
0768: }
0769:
0770: //#j2me}
0771:
0772: /**
0773: * Parse general time value from text. Time values are expected to be in W3C
0774: * XML Schema standard format as hh:mm:ss.fff, with optional leading sign
0775: * and trailing time zone.
0776: *
0777: * @param text text to be parsed
0778: * @param start offset of first character of time value
0779: * @param length number of characters in time value
0780: * @return converted time as millisecond value
0781: * @throws JiBXException on parse error
0782: */
0783: public static long parseTime(String text, int start, int length)
0784: throws JiBXException {
0785:
0786: // validate time value following date
0787: long milli = 0;
0788: boolean valid = length > (start + 7)
0789: && (text.charAt(start + 2) == ':')
0790: && (text.charAt(start + 5) == ':');
0791: if (valid) {
0792: int hour = parseDigits(text, start, 2);
0793: int minute = parseDigits(text, start + 3, 2);
0794: int second = parseDigits(text, start + 6, 2);
0795: if (hour > 23 || minute > 59 || second > 60) {
0796: valid = false;
0797: } else {
0798:
0799: // convert to base millisecond in day
0800: milli = (((hour * 60) + minute) * 60 + second) * 1000;
0801: start += 8;
0802: if (length > start) {
0803:
0804: // adjust for time zone
0805: if (text.charAt(length - 1) == 'Z') {
0806: length--;
0807: } else {
0808: char chr = text.charAt(length - 6);
0809: if (chr == '-' || chr == '+') {
0810: hour = parseDigits(text, length - 5, 2);
0811: minute = parseDigits(text, length - 2, 2);
0812: if (hour > 23 || minute > 59) {
0813: valid = false;
0814: } else {
0815: int offset = ((hour * 60) + minute) * 60 * 1000;
0816: if (chr == '-') {
0817: milli += offset;
0818: } else {
0819: milli -= offset;
0820: }
0821: }
0822: length -= 6;
0823: }
0824: }
0825:
0826: // check for trailing fractional second
0827: if (text.charAt(start) == '.') {
0828: double fraction = Double.parseDouble(text
0829: .substring(start, length));
0830: milli += fraction * 1000.0;
0831: } else if (length > start) {
0832: valid = false;
0833: }
0834: }
0835: }
0836: }
0837:
0838: // check for valid result
0839: if (valid) {
0840: return milli;
0841: } else {
0842: throw new JiBXException("Invalid dateTime format");
0843: }
0844: }
0845:
0846: /**
0847: * Parse general dateTime value from text. Date values are expected to be in
0848: * W3C XML Schema standard format as CCYY-MM-DDThh:mm:ss.fff, with optional
0849: * leading sign and trailing time zone.
0850: *
0851: * @param text text to be parsed
0852: * @return converted date as millisecond value
0853: * @throws JiBXException on parse error
0854: */
0855: public static long parseDateTime(String text) throws JiBXException {
0856:
0857: // split text to convert portions separately
0858: int split = text.indexOf('T');
0859: if (split < 0) {
0860: throw new JiBXException("Missing 'T' separator in dateTime");
0861: }
0862: return parseDate(text.substring(0, split))
0863: + parseTime(text, split + 1, text.length());
0864: }
0865:
0866: /**
0867: * Deserialize date from general dateTime text. Date values are expected to
0868: * match W3C XML Schema standard format as CCYY-MM-DDThh:mm:ss, with
0869: * optional leading minus sign and trailing seconds decimal, as necessary.
0870: * This method follows standard JiBX deserializer usage requirements by
0871: * accepting a <code>null</code> input.
0872: *
0873: * @param text text to be parsed (may be <code>null</code>)
0874: * @return converted date, or <code>null</code> if passed <code>null</code>
0875: * input
0876: * @throws JiBXException on parse error
0877: */
0878: public static Date deserializeDateTime(String text)
0879: throws JiBXException {
0880: if (text == null) {
0881: return null;
0882: } else {
0883: return new Date(parseDateTime(text));
0884: }
0885: }
0886:
0887: //#!j2me{
0888: /**
0889: * Deserialize timestamp from general dateTime text. Timestamp values are
0890: * represented in the same way as regular dates, but allow more precision in
0891: * the fractional second value (down to nanoseconds). This method follows
0892: * standard JiBX deserializer usage requirements by accepting a
0893: * <code>null</code> input.
0894: *
0895: * @param text text to be parsed (may be <code>null</code>)
0896: * @return converted timestamp, or <code>null</code> if passed
0897: * <code>null</code> input
0898: * @throws JiBXException on parse error
0899: */
0900: public static Timestamp deserializeTimestamp(String text)
0901: throws JiBXException {
0902: if (text == null) {
0903: return null;
0904: } else {
0905:
0906: // check for fractional second value present
0907: int split = text.indexOf('.');
0908: int nano = 0;
0909: if (split > 0) {
0910:
0911: // make sure there aren't multiple decimal points
0912: if (text.indexOf('.', split + 1) > 0) {
0913: throw new JiBXException(
0914: "Not a valid timestamp value");
0915: }
0916:
0917: // scan through all digits following decimal point
0918: int limit = text.length();
0919: int scan = split;
0920: while (++scan < limit) {
0921: char chr = text.charAt(scan);
0922: if (chr < '0' || chr > '9') {
0923: break;
0924: }
0925: }
0926:
0927: // parse digits following decimal point
0928: int length = scan - split - 1;
0929: if (length > 9) {
0930: length = 9;
0931: }
0932: nano = parseDigits(text, split + 1, length);
0933:
0934: // convert to number of nanoseconds
0935: while (length < 9) {
0936: nano *= 10;
0937: length++;
0938: }
0939:
0940: // strip fractional second off text
0941: if (scan < limit) {
0942: text = text.substring(0, split)
0943: + text.substring(scan);
0944: } else {
0945: text = text.substring(0, split);
0946: }
0947: }
0948:
0949: // return timestamp value with nanoseconds
0950: Timestamp stamp = new Timestamp(parseDateTime(text));
0951: stamp.setNanos(nano);
0952: return stamp;
0953: }
0954: }
0955:
0956: /**
0957: * Deserialize time from text. Time values obey the rules of the time
0958: * portion of a dataTime value. This method follows standard JiBX
0959: * deserializer usage requirements by accepting a <code>null</code> input.
0960: *
0961: * @param text text to be parsed (may be <code>null</code>)
0962: * @return converted time, or <code>null</code> if passed <code>null</code>
0963: * input
0964: * @throws JiBXException on parse error
0965: */
0966: public static Time deserializeSqlTime(String text)
0967: throws JiBXException {
0968: if (text == null) {
0969: return null;
0970: } else {
0971: return new Time(parseTime(text, 0, text.length()));
0972: }
0973: }
0974:
0975: //#j2me}
0976:
0977: /**
0978: * Format year number consistent with W3C XML Schema definitions, using a
0979: * minimum of four digits padded with zeros if necessary. A leading minus
0980: * sign is included for years prior to 1 C.E.
0981: *
0982: * @param year number to be formatted
0983: * @param buff text formatting buffer
0984: */
0985: protected static void formatYearNumber(long year, StringBuffer buff) {
0986:
0987: // start with minus sign for dates prior to 1 C.E.
0988: if (year <= 0) {
0989: buff.append('-');
0990: year = -(year - 1);
0991: }
0992:
0993: // add padding if needed to bring to length of four
0994: if (year < 1000) {
0995: buff.append('0');
0996: if (year < 100) {
0997: buff.append('0');
0998: if (year < 10) {
0999: buff.append('0');
1000: }
1001: }
1002: }
1003:
1004: // finish by converting the actual year number
1005: buff.append(year);
1006: }
1007:
1008: /**
1009: * Format a positive number as two digits. This uses an optional leading
1010: * zero digit for values less than ten.
1011: *
1012: * @param value number to be formatted (<code>0</code> to <code>99</code>)
1013: * @param buff text formatting buffer
1014: */
1015: protected static void formatTwoDigits(int value, StringBuffer buff) {
1016: if (value < 10) {
1017: buff.append('0');
1018: }
1019: buff.append(value);
1020: }
1021:
1022: /**
1023: * Format time in milliseconds to year number. The resulting year number
1024: * format is consistent with W3C XML Schema definitions, using a minimum
1025: * of four digits padded with zeros if necessary. A leading minus sign is
1026: * included for years prior to 1 C.E.
1027: *
1028: * @param value time in milliseconds to be converted (from 1 C.E.)
1029: * @param buff text formatting buffer
1030: */
1031: protected static void formatYear(long value, StringBuffer buff) {
1032:
1033: // find the actual year and month number; this uses a integer arithmetic
1034: // conversion based on Baum, first making the millisecond count
1035: // relative to March 1 of the year 0 C.E., then using simple arithmetic
1036: // operations to compute century, year, and month; it's slightly
1037: // different for pre-C.E. values because of Java's handling of divisions.
1038: long time = value + 306 * LMSPERDAY + LMSPERDAY * 3 / 4;
1039: long century = time / MSPERCENTURY; // count of centuries
1040: long adjusted = time + (century - (century / 4)) * MSPERDAY;
1041: int year = (int) (adjusted / MSPERAVGYEAR); // year in March 1 terms
1042: if (adjusted < 0) {
1043: year--;
1044: }
1045: long yms = adjusted + LMSPERDAY / 4 - (year * 365 + year / 4)
1046: * LMSPERDAY;
1047: int yday = (int) (yms / LMSPERDAY); // day number in year
1048: int month = (5 * yday + 456) / 153; // (biased) month number
1049: if (month > 12) { // convert start of year
1050: year++;
1051: }
1052:
1053: // format year to text
1054: formatYearNumber(year, buff);
1055: }
1056:
1057: /**
1058: * Format time in milliseconds to year number and month number. The
1059: * resulting year number format is consistent with W3C XML Schema
1060: * definitions, using a minimum of four digits for the year and exactly
1061: * two digits for the month.
1062: *
1063: * @param value time in milliseconds to be converted (from 1 C.E.)
1064: * @param buff text formatting buffer
1065: * @return number of milliseconds into month
1066: */
1067: protected static long formatYearMonth(long value, StringBuffer buff) {
1068:
1069: // find the actual year and month number; this uses a integer arithmetic
1070: // conversion based on Baum, first making the millisecond count
1071: // relative to March 1 of the year 0 C.E., then using simple arithmetic
1072: // operations to compute century, year, and month; it's slightly
1073: // different for pre-C.E. values because of Java's handling of divisions.
1074: long time = value + 306 * LMSPERDAY + LMSPERDAY * 3 / 4;
1075: long century = time / MSPERCENTURY; // count of centuries
1076: long adjusted = time + (century - (century / 4)) * MSPERDAY;
1077: int year = (int) (adjusted / MSPERAVGYEAR); // year in March 1 terms
1078: if (adjusted < 0) {
1079: year--;
1080: }
1081: long yms = adjusted + LMSPERDAY / 4 - (year * 365 + year / 4)
1082: * LMSPERDAY;
1083: int yday = (int) (yms / LMSPERDAY); // day number in year
1084: if (yday == 0) { // special for negative
1085: boolean bce = year < 0;
1086: if (bce) {
1087: year--;
1088: }
1089: int dcnt = year % 4 == 0 ? 366 : 365;
1090: if (!bce) {
1091: year--;
1092: }
1093: yms += dcnt * LMSPERDAY;
1094: yday += dcnt;
1095: }
1096: int month = (5 * yday + 456) / 153; // (biased) month number
1097: long rem = yms - BIAS_MONTHMS[month] - LMSPERDAY; // ms into month
1098: if (month > 12) { // convert start of year
1099: year++;
1100: month -= 12;
1101: }
1102:
1103: // format year and month as text
1104: formatYearNumber(year, buff);
1105: buff.append('-');
1106: formatTwoDigits(month, buff);
1107:
1108: // return extra milliseconds into month
1109: return rem;
1110: }
1111:
1112: /**
1113: * Format time in milliseconds to year number, month number, and day
1114: * number. The resulting year number format is consistent with W3C XML
1115: * Schema definitions, using a minimum of four digits for the year and
1116: * exactly two digits each for the month and day.
1117: *
1118: * @param value time in milliseconds to be converted (from 1 C.E.)
1119: * @param buff text formatting buffer
1120: * @return number of milliseconds into day
1121: */
1122: protected static int formatYearMonthDay(long value,
1123: StringBuffer buff) {
1124:
1125: // convert year and month
1126: long extra = formatYearMonth(value, buff);
1127:
1128: // append the day of month
1129: int day = (int) (extra / MSPERDAY) + 1;
1130: buff.append('-');
1131: formatTwoDigits(day, buff);
1132:
1133: // return excess of milliseconds into day
1134: return (int) (extra % MSPERDAY);
1135: }
1136:
1137: /**
1138: * Serialize time to general gYear text. Date values are formatted in
1139: * W3C XML Schema standard format as CCYY, with optional
1140: * leading sign included if necessary.
1141: *
1142: * @param time time to be converted, as milliseconds from January 1, 1970
1143: * @return converted gYear text
1144: */
1145: public static String serializeYear(long time) {
1146: StringBuffer buff = new StringBuffer(6);
1147: formatYear(time + TIME_BASE, buff);
1148: return buff.toString();
1149: }
1150:
1151: /**
1152: * Serialize date to general gYear text. Date values are formatted in
1153: * W3C XML Schema standard format as CCYY, with optional
1154: * leading sign included if necessary.
1155: *
1156: * @param date date to be converted
1157: * @return converted gYear text
1158: */
1159: public static String serializeYear(Date date) {
1160: return serializeYear(date.getTime());
1161: }
1162:
1163: /**
1164: * Serialize time to general gYearMonth text. Date values are formatted in
1165: * W3C XML Schema standard format as CCYY-MM, with optional
1166: * leading sign included if necessary.
1167: *
1168: * @param time time to be converted, as milliseconds from January 1, 1970
1169: * @return converted gYearMonth text
1170: */
1171: public static String serializeYearMonth(long time) {
1172: StringBuffer buff = new StringBuffer(12);
1173: formatYearMonth(time + TIME_BASE, buff);
1174: return buff.toString();
1175: }
1176:
1177: /**
1178: * Serialize date to general gYearMonth text. Date values are formatted in
1179: * W3C XML Schema standard format as CCYY-MM, with optional
1180: * leading sign included if necessary.
1181: *
1182: * @param date date to be converted
1183: * @return converted gYearMonth text
1184: */
1185: public static String serializeYearMonth(Date date) {
1186: return serializeYearMonth(date.getTime());
1187: }
1188:
1189: /**
1190: * Serialize time to general date text. Date values are formatted in
1191: * W3C XML Schema standard format as CCYY-MM-DD, with optional
1192: * leading sign included if necessary.
1193: *
1194: * @param time time to be converted, as milliseconds from January 1, 1970
1195: * @return converted date text
1196: */
1197: public static String serializeDate(long time) {
1198: StringBuffer buff = new StringBuffer(12);
1199: formatYearMonthDay(time + TIME_BASE, buff);
1200: return buff.toString();
1201: }
1202:
1203: /**
1204: * Serialize date to general date text. Date values are formatted in
1205: * W3C XML Schema standard format as CCYY-MM-DD, with optional
1206: * leading sign included if necessary.
1207: *
1208: * @param date date to be converted
1209: * @return converted date text
1210: */
1211: public static String serializeDate(Date date) {
1212: return serializeDate(date.getTime());
1213: }
1214:
1215: //#!j2me{
1216: /**
1217: * Serialize SQL date to general date text. Date values are formatted in
1218: * W3C XML Schema standard format as CCYY-MM-DD, with optional
1219: * leading sign included if necessary.
1220: *
1221: * @param date date to be converted
1222: * @return converted date text
1223: */
1224: public static String serializeSqlDate(java.sql.Date date) {
1225:
1226: // values should be normalized to midnight in zone, but no guarantee
1227: // so convert using the lame calendar approach
1228: GregorianCalendar cal = new GregorianCalendar();
1229: cal.setGregorianChange(BEGINNING_OF_TIME);
1230: cal.setTime(date);
1231: StringBuffer buff = new StringBuffer(12);
1232: int year = cal.get(Calendar.YEAR);
1233: if (date.getTime() < 0) {
1234: if (cal.get(Calendar.ERA) == GregorianCalendar.BC) {
1235: year = -year + 1;
1236: }
1237: }
1238: formatYearNumber(year, buff);
1239: buff.append('-');
1240: formatTwoDigits(cal.get(Calendar.MONTH) + 1, buff);
1241: buff.append('-');
1242: formatTwoDigits(cal.get(Calendar.DAY_OF_MONTH), buff);
1243: return buff.toString();
1244: }
1245:
1246: //#j2me}
1247:
1248: /**
1249: * Serialize time to general time text in buffer. Time values are formatted
1250: * in W3C XML Schema standard format as hh:mm:ss, with optional trailing
1251: * seconds decimal, as necessary. This form uses a supplied buffer to
1252: * support flexible use, including with dateTime combination values.
1253: *
1254: * @param time time to be converted, as milliseconds in day
1255: * @param buff buffer for appending time text
1256: */
1257: public static void serializeTime(int time, StringBuffer buff) {
1258:
1259: // append the hour, minute, and second
1260: formatTwoDigits(time / MSPERHOUR, buff);
1261: time = time % MSPERHOUR;
1262: buff.append(':');
1263: formatTwoDigits(time / MSPERMINUTE, buff);
1264: time = time % MSPERMINUTE;
1265: buff.append(':');
1266: formatTwoDigits(time / 1000, buff);
1267: time = time % 1000;
1268:
1269: // check if decimals needed on second
1270: if (time > 0) {
1271: buff.append('.');
1272: buff.append(time / 100);
1273: time = time % 100;
1274: if (time > 0) {
1275: buff.append(time / 10);
1276: time = time % 10;
1277: if (time > 0) {
1278: buff.append(time);
1279: }
1280: }
1281: }
1282: }
1283:
1284: /**
1285: * Serialize time to general dateTime text. Date values are formatted in
1286: * W3C XML Schema standard format as CCYY-MM-DDThh:mm:ss, with optional
1287: * leading sign and trailing seconds decimal, as necessary.
1288: *
1289: * @param time time to be converted, as milliseconds from January 1, 1970
1290: * @param zone flag for trailing 'Z' to be appended to indicate UTC
1291: * @return converted dateTime text
1292: */
1293: public static String serializeDateTime(long time, boolean zone) {
1294:
1295: // start with the year, month, and day
1296: StringBuffer buff = new StringBuffer(25);
1297: int extra = formatYearMonthDay(time + TIME_BASE, buff);
1298:
1299: // append the time for full form
1300: buff.append('T');
1301: serializeTime(extra, buff);
1302:
1303: // return full text with optional trailing zone indicator
1304: if (zone) {
1305: buff.append('Z');
1306: }
1307: return buff.toString();
1308: }
1309:
1310: /**
1311: * Serialize time to general dateTime text. This method is provided for
1312: * backward compatibility. It generates the dateTime text without the
1313: * trailing 'Z' to indicate UTC.
1314: *
1315: * @param time time to be converted, as milliseconds from January 1, 1970
1316: * @return converted dateTime text
1317: */
1318: public static String serializeDateTime(long time) {
1319: return serializeDateTime(time, false);
1320: }
1321:
1322: /**
1323: * Serialize date to general dateTime text. Date values are formatted in
1324: * W3C XML Schema standard format as CCYY-MM-DDThh:mm:ssZ, with optional
1325: * leading sign and trailing seconds decimal, as necessary.
1326: *
1327: * @param date date to be converted
1328: * @return converted dateTime text
1329: */
1330: public static String serializeDateTime(Date date) {
1331: return serializeDateTime(date.getTime(), true);
1332: }
1333:
1334: //#!j2me{
1335: /**
1336: * Serialize timestamp to general dateTime text. Timestamp values are
1337: * represented in the same way as regular dates, but allow more precision in
1338: * the fractional second value (down to nanoseconds).
1339: *
1340: * @param stamp timestamp to be converted
1341: * @return converted dateTime text
1342: */
1343: public static String serializeTimestamp(Timestamp stamp) {
1344:
1345: // check for nanosecond value to be included
1346: int nano = stamp.getNanos();
1347: if (nano > 0) {
1348:
1349: // convert the number of nanoseconds to text
1350: String value = serializeInt(nano);
1351:
1352: // pad with leading zeros if less than 9 digits
1353: StringBuffer digits = new StringBuffer(9);
1354: if (value.length() < 9) {
1355: int lead = 9 - value.length();
1356: for (int i = 0; i < lead; i++) {
1357: digits.append('0');
1358: }
1359: }
1360: digits.append(value);
1361:
1362: // strip trailing zeros from value
1363: int last = 9;
1364: while (--last >= 0) {
1365: if (digits.charAt(last) != '0') {
1366: break;
1367: }
1368: }
1369: digits.setLength(last + 1);
1370:
1371: // finish by appending to rounded time with decimal separator
1372: long time = stamp.getTime();
1373: return serializeDateTime(time - time % 1000, false) + '.'
1374: + digits + 'Z';
1375:
1376: } else {
1377: return serializeDateTime(stamp.getTime(), true);
1378: }
1379: }
1380:
1381: /**
1382: * Serialize time to standard text. Time values are formatted in W3C XML
1383: * Schema standard format as hh:mm:ss, with optional trailing seconds
1384: * decimal, as necessary. The standard conversion does not append a time
1385: * zone indication.
1386: *
1387: * @param time time to be converted
1388: * @return converted time text
1389: */
1390: public static String serializeSqlTime(Time time) {
1391: StringBuffer buff = new StringBuffer(12);
1392: serializeTime((int) time.getTime(), buff);
1393: return buff.toString();
1394: }
1395:
1396: //#j2me}
1397:
1398: /**
1399: * General object comparison method. Don't know why Sun hasn't seen fit to
1400: * include this somewhere, but at least it's easy to write (over and over
1401: * again).
1402: *
1403: * @param a first object to be compared
1404: * @param b second object to be compared
1405: * @return <code>true</code> if both objects are <code>null</code>, or if
1406: * <code>a.equals(b)</code>; <code>false</code> otherwise
1407: */
1408: public static boolean isEqual(Object a, Object b) {
1409: return (a == null) ? b == null : a.equals(b);
1410: }
1411:
1412: /**
1413: * Find text value in enumeration. This first does a binary search through
1414: * an array of allowed text matches. If a separate array of corresponding
1415: * values is supplied, the value at the matched position is returned;
1416: * otherwise the match index is returned directly.
1417: *
1418: * @param target text to be found in enumeration
1419: * @param enums ordered array of texts included in enumeration
1420: * @param vals array of values to be returned for corresponding text match
1421: * positions (position returned directly if this is <code>null</code>)
1422: * @return enumeration value for target text
1423: * @throws JiBXException if target text not found in enumeration
1424: */
1425: public static int enumValue(String target, String[] enums,
1426: int[] vals) throws JiBXException {
1427: int base = 0;
1428: int limit = enums.length - 1;
1429: while (base <= limit) {
1430: int cur = (base + limit) >> 1;
1431: int diff = target.compareTo(enums[cur]);
1432: if (diff < 0) {
1433: limit = cur - 1;
1434: } else if (diff > 0) {
1435: base = cur + 1;
1436: } else if (vals != null) {
1437: return vals[cur];
1438: } else {
1439: return cur;
1440: }
1441: }
1442: throw new JiBXException("Target value \"" + target
1443: + "\" not found in enumeration");
1444: }
1445:
1446: /**
1447: * Decode a chunk of data from base64 encoding. The length of a chunk is
1448: * always 4 characters in the base64 representation, but may be 1, 2, or 3
1449: * bytes of data, as determined by whether there are any pad characters at
1450: * the end of the base64 representation
1451: *
1452: * @param base starting offset within base64 character array
1453: * @param chrs character array for base64 text representation
1454: * @param fill starting offset within byte data array
1455: * @param byts byte data array
1456: * @return number of decoded bytes
1457: */
1458: private static int decodeChunk(int base, char[] chrs, int fill,
1459: byte[] byts) {
1460:
1461: // find the byte count to be decoded
1462: int length = 3;
1463: if (chrs[base + 3] == PAD_CHAR) {
1464: length = 2;
1465: if (chrs[base + 2] == PAD_CHAR) {
1466: length = 1;
1467: }
1468: }
1469:
1470: // get 6-bit values
1471: int v0 = s_base64Values[chrs[base + 0]];
1472: int v1 = s_base64Values[chrs[base + 1]];
1473: int v2 = s_base64Values[chrs[base + 2]];
1474: int v3 = s_base64Values[chrs[base + 3]];
1475:
1476: // convert and store bytes of data
1477: switch (length) {
1478: case 3:
1479: byts[fill + 2] = (byte) (v2 << 6 | v3);
1480: case 2:
1481: byts[fill + 1] = (byte) (v1 << 4 | v2 >> 2);
1482: case 1:
1483: byts[fill] = (byte) (v0 << 2 | v1 >> 4);
1484: break;
1485: }
1486: return length;
1487: }
1488:
1489: /**
1490: * Parse base64 data from text. This converts the base64 data into a byte
1491: * array of the appopriate length. In keeping with the recommendations,
1492: *
1493: * @param text text to be parsed (may include extra characters)
1494: * @return byte array of data
1495: * @throws JiBXException if invalid character in base64 representation
1496: */
1497: public static byte[] parseBase64(String text) throws JiBXException {
1498:
1499: // convert raw text to base64 character array
1500: char[] chrs = new char[text.length()];
1501: int length = 0;
1502: for (int i = 0; i < text.length(); i++) {
1503: char chr = text.charAt(i);
1504: if (chr < 128 && s_base64Values[chr] >= 0) {
1505: chrs[length++] = chr;
1506: }
1507: }
1508:
1509: // check the text length
1510: if (length % 4 != 0) {
1511: throw new JiBXException(
1512: "Text length for base64 must be a multiple of 4");
1513: } else if (length == 0) {
1514: return new byte[0];
1515: }
1516:
1517: // find corresponding byte count for data
1518: int blength = length / 4 * 3;
1519: if (chrs[length - 1] == PAD_CHAR) {
1520: blength--;
1521: if (chrs[length - 2] == PAD_CHAR) {
1522: blength--;
1523: }
1524: }
1525:
1526: // convert text to actual bytes of data
1527: byte[] byts = new byte[blength];
1528: int fill = 0;
1529: for (int i = 0; i < length; i += 4) {
1530: fill += decodeChunk(i, chrs, fill, byts);
1531: }
1532: if (fill != blength) {
1533: throw new JiBXException(
1534: "Embedded padding characters in byte64 text");
1535: }
1536: return byts;
1537: }
1538:
1539: /**
1540: * Parse base64 data from text. This converts the base64 data into a byte
1541: * array of the appopriate length. In keeping with the recommendations,
1542: *
1543: * @param text text to be parsed (may be null, or include extra characters)
1544: * @return byte array of data
1545: * @throws JiBXException if invalid character in base64 representation
1546: */
1547: public static byte[] deserializeBase64(String text)
1548: throws JiBXException {
1549: if (text == null) {
1550: return null;
1551: } else {
1552: return parseBase64(text);
1553: }
1554: }
1555:
1556: /**
1557: * Encode a chunk of data to base64 encoding. Converts the next three bytes
1558: * of data into four characters of text representation, using padding at the
1559: * end of less than three bytes of data remain.
1560: *
1561: * @param base starting offset within byte array
1562: * @param byts byte data array
1563: * @param buff buffer for encoded text
1564: */
1565: public static void encodeChunk(int base, byte[] byts,
1566: StringBuffer buff) {
1567:
1568: // get actual byte data length to be encoded
1569: int length = 3;
1570: if (base + length > byts.length) {
1571: length = byts.length - base;
1572: }
1573:
1574: // convert up to three bytes of data to four characters of text
1575: int b0 = byts[base];
1576: int value = (b0 >> 2) & 0x3F;
1577: buff.append(s_base64Chars[value]);
1578: if (length > 1) {
1579: int b1 = byts[base + 1];
1580: value = ((b0 & 3) << 4) + ((b1 >> 4) & 0x0F);
1581: buff.append(s_base64Chars[value]);
1582: if (length > 2) {
1583: int b2 = byts[base + 2];
1584: value = ((b1 & 0x0F) << 2) + ((b2 >> 6) & 3);
1585: buff.append(s_base64Chars[value]);
1586: value = b2 & 0x3F;
1587: buff.append(s_base64Chars[value]);
1588: } else {
1589: value = (b1 & 0x0F) << 2;
1590: buff.append(s_base64Chars[value]);
1591: buff.append(PAD_CHAR);
1592: }
1593: } else {
1594: value = (b0 & 3) << 4;
1595: buff.append(s_base64Chars[value]);
1596: buff.append(PAD_CHAR);
1597: buff.append(PAD_CHAR);
1598: }
1599: }
1600:
1601: /**
1602: * Serialize byte array to base64 text. In keeping with the specification,
1603: * this adds a line break every 76 characters in the encoded representation.
1604: *
1605: * @param byts byte data array
1606: * @return base64 encoded text
1607: */
1608: public static String serializeBase64(byte[] byts) {
1609: StringBuffer buff = new StringBuffer((byts.length + 2) / 3 * 4);
1610: for (int i = 0; i < byts.length; i += 3) {
1611: encodeChunk(i, byts, buff);
1612: if (i > 0 && i % 57 == 0 && (i + 3) < byts.length) {
1613: buff.append("\r\n");
1614: }
1615: }
1616: return buff.toString();
1617: }
1618:
1619: /**
1620: * Resize array of arbitrary type.
1621: *
1622: * @param size new aray size
1623: * @param base array to be resized
1624: * @return resized array, with all data to minimum of the two sizes copied
1625: */
1626: public static Object resizeArray(int size, Object base) {
1627: int prior = Array.getLength(base);
1628: if (size == prior) {
1629: return base;
1630: } else {
1631: Class type = base.getClass().getComponentType();
1632: Object copy = Array.newInstance(type, size);
1633: int count = Math.min(size, prior);
1634: System.arraycopy(base, 0, copy, 0, count);
1635: return copy;
1636: }
1637: }
1638:
1639: /**
1640: * Grow array of arbitrary type. The returned array is double the size of
1641: * the original array, providing this is at least the defined minimum size.
1642: *
1643: * @param base array to be grown
1644: * @return array of twice the size as original array, with all data copied
1645: */
1646: public static Object growArray(Object base) {
1647: int length = Array.getLength(base);
1648: Class type = base.getClass().getComponentType();
1649: int newlen = Math.max(length * 2, MINIMUM_GROWN_ARRAY_SIZE);
1650: Object copy = Array.newInstance(type, newlen);
1651: System.arraycopy(base, 0, copy, 0, length);
1652: return copy;
1653: }
1654:
1655: /**
1656: * Factory method to create a <code>java.util.ArrayList</code> as the
1657: * implementation of a <code>java.util.List</code>.
1658: *
1659: * @return new <code>java.util.ArrayList</code>
1660: */
1661: public static List arrayListFactory() {
1662: return new ArrayList();
1663: }
1664:
1665: /**
1666: * Convert whitespace-separated list of values. This implements the
1667: * whitespace skipping, calling the supplied item deserializer for handling
1668: * each individual value.
1669: *
1670: * @param text value to be converted
1671: * @param ideser list item deserializer
1672: * @return list of deserialized items (<code>null</code> if input
1673: * <code>null</code>, or if nonrecoverable error)
1674: * @throws JiBXException if error in deserializing text
1675: */
1676: public static ArrayList deserializeList(String text,
1677: IListItemDeserializer ideser) throws JiBXException {
1678: if (text == null) {
1679: return null;
1680: } else {
1681:
1682: // scan text to find whitespace breaks between items
1683: ArrayList items = new ArrayList();
1684: int length = text.length();
1685: int base = 0;
1686: boolean space = true;
1687: for (int i = 0; i < length; i++) {
1688: char chr = text.charAt(i);
1689: switch (chr) {
1690:
1691: case 0x09:
1692: case 0x0A:
1693: case 0x0D:
1694: case ' ':
1695: // ignore if preceded by space
1696: if (!space) {
1697: String itext = text.substring(base, i);
1698: items.add(ideser.deserialize(itext));
1699: space = true;
1700: }
1701: base = i + 1;
1702: break;
1703:
1704: default:
1705: space = false;
1706: break;
1707: }
1708: }
1709:
1710: // finish last item
1711: if (base < length) {
1712: String itext = text.substring(base);
1713: items.add(ideser.deserialize(itext));
1714: }
1715:
1716: // check if any items found
1717: if (items.size() > 0) {
1718: return items;
1719: } else {
1720: return null;
1721: }
1722: }
1723: }
1724:
1725: /**
1726: * Deserialize a list of whitespace-separated tokens into an array of
1727: * strings.
1728: *
1729: * @param text value text
1730: * @return created class instance
1731: * @throws JiBXException on error in marshalling
1732: */
1733: public static String[] deserializeTokenList(String text)
1734: throws JiBXException {
1735:
1736: // use basic qualified name deserializer to handle items
1737: IListItemDeserializer ldser = new IListItemDeserializer() {
1738: public Object deserialize(String text) throws JiBXException {
1739: return text;
1740: }
1741: };
1742: ArrayList list = Utility.deserializeList(text, ldser);
1743: if (list == null) {
1744: return null;
1745: } else {
1746: return (String[]) list.toArray(new String[list.size()]);
1747: }
1748: }
1749:
1750: /**
1751: * Serialize an array of strings into a whitespace-separated token list.
1752: *
1753: * @param tokens array of strings to be serialized
1754: * @return list text
1755: */
1756: public static String serializeTokenList(String[] tokens) {
1757: StringBuffer buff = new StringBuffer();
1758: for (int i = 0; i < tokens.length; i++) {
1759: if (buff.length() > 0) {
1760: buff.append(' ');
1761: }
1762: buff.append(tokens[i]);
1763: }
1764: return buff.toString();
1765: }
1766: }
|