KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jibx > runtime > Utility


1 /*
2 Copyright (c) 2002-2004, Dennis M. Sosnoski.
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without modification,
6 are permitted provided that the following conditions are met:
7
8  * Redistributions of source code must retain the above copyright notice, this
9    list of conditions and the following disclaimer.
10  * Redistributions in binary form must reproduce the above copyright notice,
11    this list of conditions and the following disclaimer in the documentation
12    and/or other materials provided with the distribution.
13  * Neither the name of JiBX nor the names of its contributors may be used
14    to endorse or promote products derived from this software without specific
15    prior written permission.
16
17 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
21 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
24 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */

28
29 package org.jibx.runtime;
30
31 import java.sql.Time JavaDoc;
32 import java.sql.Timestamp JavaDoc;
33 import java.util.*;
34
35 /**
36  * Utility class supplying static methods. Date serialization is based on the
37  * algorithms published by Peter Baum (http://www.capecod.net/~pbaum). All date
38  * handling is done according to the W3C Schema specification, which uses a
39  * proleptic Gregorian calendar with no year 0. Note that this differs from the
40  * Java date handling, which uses a discontinuous Gregorian calendar.
41  *
42  * @author Dennis M. Sosnoski
43  * @version 1.0
44  */

45
46 public abstract class Utility
47 {
48     /** Number of milliseconds in a minute. */
49     private static final int MSPERMINUTE = 60000;
50     
51     /** Number of milliseconds in an hour. */
52     private static final int MSPERHOUR = MSPERMINUTE*60;
53     
54     /** Number of milliseconds in a day. */
55     private static final int MSPERDAY = MSPERHOUR*24;
56     
57     /** Number of milliseconds in a day as a long. */
58     private static final long LMSPERDAY = (long)MSPERDAY;
59     
60     /** Number of milliseconds in a (non-leap) year. */
61     private static final long MSPERYEAR = LMSPERDAY*365;
62     
63     /** Average number of milliseconds in a year within century. */
64     private static final long MSPERAVGYEAR = (long)(MSPERDAY*365.25);
65     
66     /** Number of milliseconds in a normal century. */
67     private static final long MSPERCENTURY = (long)(MSPERDAY*36524.25);
68     
69     /** Millisecond value of base time for internal representation. This gives
70      the bias relative to January 1 of the year 1 C.E. */

71     private static final long TIME_BASE = 1969*MSPERYEAR +
72         (1969/4 - 19 + 4)*LMSPERDAY;
73     
74     /** Day number for start of month in non-leap year. */
75     private static final int[] MONTHS_NONLEAP =
76     {
77         0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
78     };
79
80     /** Day number for start of month in non-leap year. */
81     private static final int[] MONTHS_LEAP =
82     {
83         0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366
84     };
85
86     /** Millisecond count prior to start of month in March 1-biased year. */
87     private static final long[] BIAS_MONTHMS =
88     {
89         0*LMSPERDAY, 0*LMSPERDAY, 0*LMSPERDAY, 0*LMSPERDAY,
90         31*LMSPERDAY, 61*LMSPERDAY, 92*LMSPERDAY, 122*LMSPERDAY,
91         153*LMSPERDAY, 184*LMSPERDAY, 214*LMSPERDAY, 245*LMSPERDAY,
92         275*LMSPERDAY, 306*LMSPERDAY, 337*LMSPERDAY
93     };
94     
95     /** Pad character for base64 encoding. */
96     private static final char PAD_CHAR = '=';
97     
98     /** Characters used in base64 encoding. */
99     private static final char[] s_base64Chars = {
100         'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
101         'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
102         'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
103         'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
104         'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
105         'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
106         '8', '9', '+', '/'
107     };
108     
109     /** Values corresponding to characters used in bas64 encoding. */
110     private static final byte[] s_base64Values = new byte[128];
111     static {
112         for (int i = 0; i < s_base64Values.length; i++) {
113             s_base64Values[i] = -1;
114         }
115         s_base64Values[PAD_CHAR] = 0;
116         for (int i = 0; i < s_base64Chars.length; i++) {
117             s_base64Values[s_base64Chars[i]] = (byte)i;
118         }
119     }
120
121     /**
122      * Parse digits in text as integer value. This internal method is used
123      * for number values embedded within lexical structures. Only decimal
124      * digits can be included in the text range parsed.
125      *
126      * @param text text to be parsed
127      * @param offset starting offset in text
128      * @param length number of digits to be parsed
129      * @return converted positive integer value
130      * @throws JiBXException on parse error
131      */

132
133     private static int parseDigits(String JavaDoc text, int offset, int length)
134         throws JiBXException {
135
136         // check if overflow a potential problem
137
int value = 0;
138         if (length > 9) {
139
140             // use library parse code for potential overflow
141
try {
142                 value = Integer.parseInt(text.substring(offset, offset+length));
143             } catch (NumberFormatException JavaDoc ex) {
144                 throw new JiBXException(ex.getMessage());
145             }
146
147         } else {
148
149             // parse with no overflow worries
150
int limit = offset + length;
151             while (offset < limit) {
152                 char chr = text.charAt(offset++);
153                 if (chr >= '0' && chr <= '9') {
154                     value = value * 10 + (chr - '0');
155                 } else {
156                     throw new JiBXException("Non-digit in number value");
157                 }
158             }
159
160         }
161         return value;
162     }
163
164     /**
165      * Parse integer value from text. Integer values are parsed with optional
166      * leading sign flag, followed by any number of digits.
167      *
168      * @param text text to be parsed
169      * @return converted integer value
170      * @throws JiBXException on parse error
171      */

172
173     public static int parseInt(String JavaDoc text) throws JiBXException {
174
175         // make sure there's text to be processed
176
text = text.trim();
177         int offset = 0;
178         int limit = text.length();
179         if (limit == 0) {
180             throw new JiBXException("Empty number value");
181         }
182
183         // check leading sign present in text
184
boolean negate = false;
185         char chr = text.charAt(0);
186         if (chr == '-') {
187             if (limit > 9) {
188
189                 // special case to make sure maximum negative value handled
190
try {
191                     return Integer.parseInt(text);
192                 } catch (NumberFormatException JavaDoc ex) {
193                     throw new JiBXException(ex.getMessage());
194                 }
195
196             } else {
197                 negate = true;
198                 offset++;
199             }
200         } else if (chr == '+') {
201             offset++;
202         }
203         if (offset >= limit) {
204             throw new JiBXException("Invalid number format");
205         }
206
207         // handle actual value conversion
208
int value = parseDigits(text, offset, limit-offset);
209         if (negate) {
210             return -value;
211         } else {
212             return value;
213         }
214     }
215
216     /**
217      * Serialize int value to text.
218      *
219      * @param value int value to be serialized
220      * @return text representation of value
221      */

222
223     public static String JavaDoc serializeInt(int value) {
224         return Integer.toString(value);
225     }
226
227     /**
228      * Parse long value from text. Long values are parsed with optional
229      * leading sign flag, followed by any number of digits.
230      *
231      * @param text text to be parsed
232      * @return converted long value
233      * @throws JiBXException on parse error
234      */

235
236     public static long parseLong(String JavaDoc text) throws JiBXException {
237
238         // make sure there's text to be processed
239
text = text.trim();
240         int offset = 0;
241         int limit = text.length();
242         if (limit == 0) {
243             throw new JiBXException("Empty number value");
244         }
245
246         // check leading sign present in text
247
boolean negate = false;
248         char chr = text.charAt(0);
249         if (chr == '-') {
250             negate = true;
251             offset++;
252         } else if (chr == '+') {
253             offset++;
254         }
255         if (offset >= limit) {
256             throw new JiBXException("Invalid number format");
257         }
258
259         // check if overflow a potential problem
260
long value = 0;
261         if (limit-offset > 18) {
262
263             // pass text to library parse code (less leading +)
264
if (chr == '+') {
265                 text = text.substring(1);
266             }
267             try {
268                 value = Long.parseLong(text);
269             } catch (NumberFormatException JavaDoc ex) {
270                 throw new JiBXException(ex.getMessage());
271             }
272
273         } else {
274
275             // parse with no overflow worries
276
while (offset < limit) {
277                 chr = text.charAt(offset++);
278                 if (chr >= '0' && chr <= '9') {
279                     value = value * 10 + (chr - '0');
280                 } else {
281                     throw new JiBXException("Non-digit in number value");
282                 }
283             }
284             if (negate) {
285                 value = -value;
286             }
287
288         }
289         return value;
290     }
291
292     /**
293      * Serialize long value to text.
294      *
295      * @param value long value to be serialized
296      * @return text representation of value
297      */

298
299     public static String JavaDoc serializeLong(long value) {
300         return Long.toString(value);
301     }
302
303     /**
304      * Parse short value from text. Short values are parsed with optional
305      * leading sign flag, followed by any number of digits.
306      *
307      * @param text text to be parsed
308      * @return converted short value
309      * @throws JiBXException on parse error
310      */

311
312     public static short parseShort(String JavaDoc text) throws JiBXException {
313         int value = parseInt(text);
314         if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
315             throw new JiBXException("Value out of range");
316         }
317         return (short)value;
318     }
319
320     /**
321      * Serialize short value to text.
322      *
323      * @param value short value to be serialized
324      * @return text representation of value
325      */

326
327     public static String JavaDoc serializeShort(short value) {
328         return Short.toString(value);
329     }
330
331     /**
332      * Parse byte value from text. Byte values are parsed with optional
333      * leading sign flag, followed by any number of digits.
334      *
335      * @param text text to be parsed
336      * @return converted byte value
337      * @throws JiBXException on parse error
338      */

339
340     public static byte parseByte(String JavaDoc text) throws JiBXException {
341         int value = parseInt(text);
342         if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
343             throw new JiBXException("Value out of range");
344         }
345         return (byte)value;
346     }
347
348     /**
349      * Serialize byte value to text.
350      *
351      * @param value byte value to be serialized
352      * @return text representation of value
353      */

354
355     public static String JavaDoc serializeByte(byte value) {
356         return Byte.toString(value);
357     }
358
359     /**
360      * Parse boolean value from text. Boolean values are parsed as either text
361      * "true" and "false", or "1" and "0" numeric equivalents.
362      *
363      * @param text text to be parsed
364      * @return converted boolean value
365      * @throws JiBXException on parse error
366      */

367
368     public static boolean parseBoolean(String JavaDoc text) throws JiBXException {
369         text = text.trim();
370         if ("true".equals(text) || "1".equals(text)) {
371             return true;
372         } else if ("false".equals(text) || "0".equals(text)) {
373             return false;
374         } else {
375             throw new JiBXException("Invalid boolean value");
376         }
377     }
378
379     /**
380      * Serialize boolean value to text. This serializes the value using the
381      * text representation as "true" or "false".
382      *
383      * @param value boolean value to be serialized
384      * @return text representation of value
385      */

386
387     public static String JavaDoc serializeBoolean(boolean value) {
388         return value ? "true" : "false";
389     }
390
391     /**
392      * Parse char value from text as unsigned 16-bit integer. Char values are
393      * parsed with optional leading sign flag, followed by any number of digits.
394      *
395      * @param text text to be parsed
396      * @return converted char value
397      * @throws JiBXException on parse error
398      */

399
400     public static char parseChar(String JavaDoc text) throws JiBXException {
401         int value = parseInt(text);
402         if (value < Character.MIN_VALUE || value > Character.MAX_VALUE) {
403             throw new JiBXException("Value out of range");
404         }
405         return (char)value;
406     }
407
408     /**
409      * Serialize char value to text as unsigned 16-bit integer.
410      *
411      * @param value char value to be serialized
412      * @return text representation of value
413      */

414
415     public static String JavaDoc serializeChar(char value) {
416         return Integer.toString(value);
417     }
418
419     /**
420      * Parse char value from text as character value. This requires that the
421      * string must be of length one.
422      *
423      * @param text text to be parsed
424      * @return converted char value
425      * @throws JiBXException on parse error
426      */

427
428     public static char parseCharString(String JavaDoc text) throws JiBXException {
429         if (text.length() == 1) {
430             return text.charAt(0);
431         } else {
432             throw new JiBXException("Input must be a single character");
433         }
434     }
435
436     /**
437      * Deserialize char value from text as character value. This requires that
438      * the string must be null or of length one.
439      *
440      * @param text text to be parsed (may be <code>null</code>)
441      * @return converted char value
442      * @throws JiBXException on parse error
443      */

444
445     public static char deserializeCharString(String JavaDoc text) throws JiBXException {
446         if (text == null) {
447             return 0;
448         } else {
449             return parseCharString(text);
450         }
451     }
452
453     /**
454      * Serialize char value to text as string of length one.
455      *
456      * @param value char value to be serialized
457      * @return text representation of value
458      */

459
460     public static String JavaDoc serializeCharString(char value) {
461         return String.valueOf(value);
462     }
463
464     /**
465      * Parse float value from text. This uses the W3C XML Schema format for
466      * floats, with the exception that it will accept "+NaN" and "-NaN" as
467      * valid formats. This is not in strict compliance with the specification,
468      * but is included for interoperability with other Java XML processing.
469      *
470      * @param text text to be parsed
471      * @return converted float value
472      * @throws JiBXException on parse error
473      */

474
475     public static float parseFloat(String JavaDoc text) throws JiBXException {
476         text = text.trim();
477         if ("-INF".equals(text)) {
478             return Float.NEGATIVE_INFINITY;
479         } else if ("INF".equals(text)) {
480             return Float.POSITIVE_INFINITY;
481         } else {
482             try {
483                 return Float.parseFloat(text);
484             } catch (NumberFormatException JavaDoc ex) {
485                 throw new JiBXException(ex.getMessage());
486             }
487         }
488     }
489
490     /**
491      * Serialize float value to text.
492      *
493      * @param value float value to be serialized
494      * @return text representation of value
495      */

496
497     public static String JavaDoc serializeFloat(float value) {
498         if (Float.isInfinite(value)) {
499             return (value < 0.0f) ? "-INF" : "INF";
500         } else {
501             return Float.toString(value);
502         }
503     }
504
505     /**
506      * Parse double value from text. This uses the W3C XML Schema format for
507      * doubles, with the exception that it will accept "+NaN" and "-NaN" as
508      * valid formats. This is not in strict compliance with the specification,
509      * but is included for interoperability with other Java XML processing.
510      *
511      * @param text text to be parsed
512      * @return converted double value
513      * @throws JiBXException on parse error
514      */

515
516     public static double parseDouble(String JavaDoc text) throws JiBXException {
517         text = text.trim();
518         if ("-INF".equals(text)) {
519             return Double.NEGATIVE_INFINITY;
520         } else if ("INF".equals(text)) {
521             return Double.POSITIVE_INFINITY;
522         } else {
523             try {
524                 return Double.parseDouble(text);
525             } catch (NumberFormatException JavaDoc ex) {
526                 throw new JiBXException(ex.getMessage());
527             }
528         }
529     }
530
531     /**
532      * Serialize double value to text.
533      *
534      * @param value double value to be serialized
535      * @return text representation of value
536      */

537
538     public static String JavaDoc serializeDouble(double value) {
539         if (Double.isInfinite(value)) {
540             return (value < 0.0f) ? "-INF" : "INF";
541         } else {
542             return Double.toString(value);
543         }
544     }
545
546     /**
547      * Convert gYear text to Java date. Date values are expected to be in
548      * W3C XML Schema standard format as CCYY, with optional leading sign.
549      *
550      * @param text text to be parsed
551      * @return start of year date as millisecond value from 1 C.E.
552      * @throws JiBXException on parse error
553      */

554
555     public static long parseYear(String JavaDoc text) throws JiBXException {
556
557         // start by validating the length
558
text = text.trim();
559         boolean valid = true;
560         int minc = 4;
561         char chr = text.charAt(0);
562         if (chr == '-') {
563             minc = 5;
564         } else if (chr == '+') {
565             valid = false;
566         }
567         if (text.length() < minc) {
568             valid = false;
569         }
570         if (!valid) {
571             throw new JiBXException("Invalid year format");
572         }
573
574         // handle year conversion
575
int year = parseInt(text);
576         if (year == 0) {
577             throw new JiBXException("Year value 0 is not allowed");
578         }
579         if (year > 0) {
580             year--;
581         }
582         long day = ((long)year)*365 + year/4 - year/100 + year/400;
583         return day*MSPERDAY - TIME_BASE;
584     }
585
586     /**
587      * Convert gYearMonth text to Java date. Date values are expected to be in
588      * W3C XML Schema standard format as CCYY-MM, with optional
589      * leading sign.
590      *
591      * @param text text to be parsed
592      * @return start of month in year date as millisecond value
593      * @throws JiBXException on parse error
594      */

595
596     public static long parseYearMonth(String JavaDoc text) throws JiBXException {
597
598         // start by validating the length and basic format
599
text = text.trim();
600         boolean valid = true;
601         int minc = 7;
602         char chr = text.charAt(0);
603         if (chr == '-') {
604             minc = 8;
605         } else if (chr == '+') {
606             valid = false;
607         }
608         int split = text.length() - 3;
609         if (text.length() < minc) {
610             valid = false;
611         } else {
612             if (text.charAt(split) != '-') {
613                 valid = false;
614             }
615         }
616         if (!valid) {
617             throw new JiBXException("Invalid date format");
618         }
619
620         // handle year and month conversion
621
int year = parseInt(text.substring(0, split));
622         if (year == 0) {
623             throw new JiBXException("Year value 0 is not allowed");
624         }
625         int month = parseDigits(text, split+1, 2) - 1;
626         if (month < 0 || month > 11) {
627             throw new JiBXException("Month value out of range");
628         }
629         boolean leap = (year%4 == 0) && !((year%100 == 0) && (year%400 != 0));
630         if (year > 0) {
631             year--;
632         }
633         long day = ((long)year)*365 + year/4 - year/100 + year/400 +
634             (leap ? MONTHS_LEAP : MONTHS_NONLEAP)[month];
635         return day*MSPERDAY - TIME_BASE;
636     }
637
638     /**
639      * Convert date text to Java date. Date values are expected to be in
640      * W3C XML Schema standard format as CCYY-MM-DD, with optional
641      * leading sign and trailing time zone (though the time zone is ignored
642      * in this case).
643      *
644      * @param text text to be parsed
645      * @return start of day in month and year date as millisecond value
646      * @throws JiBXException on parse error
647      */

648
649     public static long parseDate(String JavaDoc text) throws JiBXException {
650
651         // start by validating the length and basic format
652
boolean valid = true;
653         int minc = 10;
654         char chr = text.charAt(0);
655         if (chr == '-') {
656             minc = 11;
657         } else if (chr == '+') {
658             valid = false;
659         }
660         int split = text.length() - 6;
661         if (text.length() < minc) {
662             valid = false;
663         } else {
664             if (text.charAt(split) != '-' || text.charAt(split+3) != '-') {
665                 valid = false;
666             }
667         }
668         if (!valid) {
669             throw new JiBXException("Invalid date format");
670         }
671
672         // handle year, month, and day conversion
673
int year = parseInt(text.substring(0, split));
674         if (year == 0) {
675             throw new JiBXException("Year value 0 is not allowed");
676         }
677         int month = parseDigits(text, split+1, 2) - 1;
678         if (month < 0 || month > 11) {
679             throw new JiBXException("Month value out of range");
680         }
681         long day = parseDigits(text, split+4, 2) - 1;
682         boolean leap = (year%4 == 0) && !((year%100 == 0) && (year%400 != 0));
683         int[] starts = leap ? MONTHS_LEAP : MONTHS_NONLEAP;
684         if (day < 0 || day >= (starts[month+1]-starts[month])) {
685             throw new JiBXException("Day value out of range");
686         }
687         if (year > 0) {
688             year--;
689         }
690         day += ((long)year)*365 + year/4 - year/100 + year/400 + starts[month];
691         return day*MSPERDAY - TIME_BASE;
692     }
693
694     /**
695      * Deserialize date from text. Date values are expected to match W3C XML
696      * Schema standard format as CCYY-MM-DD, with optional leading minus sign
697      * if necessary. This method follows standard JiBX deserializer usage
698      * requirements by accepting a <code>null</code> input.
699      *
700      * @param text text to be parsed (may be <code>null</code>)
701      * @return converted date, or <code>null</code> if passed <code>null</code>
702      * input
703      * @throws JiBXException on parse error
704      */

705
706     public static Date deserializeDate(String JavaDoc text) throws JiBXException {
707         if (text == null) {
708             return null;
709         } else {
710             return new Date(parseDate(text));
711         }
712     }
713
714     /**
715      * Deserialize SQL date from text. Date values are expected to match W3C XML
716      * Schema standard format as CCYY-MM-DD, with optional leading minus sign
717      * if necessary. This method follows standard JiBX deserializer usage
718      * requirements by accepting a <code>null</code> input.
719      *
720      * @param text text to be parsed (may be <code>null</code>)
721      * @return converted date, or <code>null</code> if passed <code>null</code>
722      * input
723      * @throws JiBXException on parse error
724      */

725
726     public static java.sql.Date JavaDoc deserializeSqlDate(String JavaDoc text)
727         throws JiBXException {
728         if (text == null) {
729             return null;
730         } else {
731             
732             // convert value, assuming maximum daylight time change of 2 hours
733
long time = parseDate(text);
734             time += TimeZone.getDefault().getRawOffset() + MSPERHOUR*2;
735             return new java.sql.Date JavaDoc(time);
736         }
737     }
738
739     /**
740      * Parse general time value from text. Time values are expected to be in W3C
741      * XML Schema standard format as hh:mm:ss.fff, with optional leading sign
742      * and trailing time zone.
743      *
744      * @param text text to be parsed
745      * @param start offset of first character of time value
746      * @param length number of characters in time value
747      * @return converted time as millisecond value
748      * @throws JiBXException on parse error
749      */

750
751     public static long parseTime(String JavaDoc text, int start, int length)
752         throws JiBXException {
753
754         // validate time value following date
755
long milli = 0;
756         boolean valid = length > (start+7) &&
757             (text.charAt(start+2) == ':') &&
758             (text.charAt(start+5) == ':');
759         if (valid) {
760             int hour = parseDigits(text, start, 2);
761             int minute = parseDigits(text, start+3, 2);
762             int second = parseDigits(text, start+6, 2);
763             if (hour > 23 || minute > 59 || second > 60) {
764                 valid = false;
765             } else {
766
767                 // convert to base millisecond in day
768
milli = (((hour*60)+minute)*60+second)*1000;
769                 start += 8;
770                 if (length > start) {
771
772                     // adjust for time zone
773
if (text.charAt(length-1) == 'Z') {
774                         length--;
775                     } else {
776                         char chr = text.charAt(length-6);
777                         if (chr == '-' || chr == '+') {
778                             hour = parseDigits(text, length-5, 2);
779                             minute = parseDigits(text, length-2, 2);
780                             if (hour > 23 || minute > 59) {
781                                 valid = false;
782                             } else {
783                                 int offset = ((hour*60)+minute)*60*1000;
784                                 if (chr == '-') {
785                                     milli += offset;
786                                 } else {
787                                     milli -= offset;
788                                 }
789                             }
790                             length -= 6;
791                         }
792                     }
793
794                     // check for trailing fractional second
795
if (text.charAt(start) == '.') {
796                         double fraction = Double.parseDouble
797                             (text.substring(start, length));
798                         milli += fraction*1000.0;
799                     } else if (length > start) {
800                         valid = false;
801                     }
802                 }
803             }
804         }
805
806         // check for valid result
807
if (valid) {
808             return milli;
809         } else {
810             throw new JiBXException("Invalid dateTime format");
811         }
812     }
813
814     /**
815      * Parse general dateTime value from text. Date values are expected to be in
816      * W3C XML Schema standard format as CCYY-MM-DDThh:mm:ss.fff, with optional
817      * leading sign and trailing time zone.
818      *
819      * @param text text to be parsed
820      * @return converted date as millisecond value
821      * @throws JiBXException on parse error
822      */

823
824     public static long parseDateTime(String JavaDoc text) throws JiBXException {
825
826         // split text to convert portions separately
827
int split = text.indexOf('T');
828         if (split < 0) {
829             throw new JiBXException("Missing 'T' separator in dateTime");
830         }
831         return parseDate(text.substring(0, split)) +
832             parseTime(text, split+1, text.length());
833     }
834
835     /**
836      * Deserialize date from general dateTime text. Date values are expected to
837      * match W3C XML Schema standard format as CCYY-MM-DDThh:mm:ss, with
838      * optional leading minus sign and trailing seconds decimal, as necessary.
839      * This method follows standard JiBX deserializer usage requirements by
840      * accepting a <code>null</code> input.
841      *
842      * @param text text to be parsed (may be <code>null</code>)
843      * @return converted date, or <code>null</code> if passed <code>null</code>
844      * input
845      * @throws JiBXException on parse error
846      */

847
848     public static Date deserializeDateTime(String JavaDoc text) throws JiBXException {
849         if (text == null) {
850             return null;
851         } else {
852             return new Date(parseDateTime(text));
853         }
854     }
855
856     /**
857      * Deserialize timestamp from general dateTime text. Timestamp values are
858      * represented in the same way as regular dates, but allow more precision in
859      * the fractional second value (down to nanoseconds). This method follows
860      * standard JiBX deserializer usage requirements by accepting a
861      * <code>null</code> input.
862      *
863      * @param text text to be parsed (may be <code>null</code>)
864      * @return converted timestamp, or <code>null</code> if passed
865      * <code>null</code> input
866      * @throws JiBXException on parse error
867      */

868
869     public static Timestamp JavaDoc deserializeTimestamp(String JavaDoc text)
870         throws JiBXException {
871         if (text == null) {
872             return null;
873         } else {
874             
875             // check for fractional second value present
876
int split = text.indexOf('.');
877             int nano = 0;
878             if (split > 0) {
879                 
880                 // make sure there aren't multiple decimal points
881
if (text.indexOf('.', split) > 0) {
882                     throw new JiBXException("Not a valid dateTime value");
883                 }
884                 
885                 // scan through all digits following decimal point
886
int limit = text.length();
887                 int scan = split;
888                 while (++scan < limit) {
889                     char chr = text.charAt(scan);
890                     if (chr < '0' || chr > '9') {
891                         break;
892                     }
893                 }
894                 
895                 // parse digits following decimal point
896
int length = scan - split - 1;
897                 if (length > 9) {
898                     length = 9;
899                 }
900                 nano = parseDigits(text, split+1, length);
901                 
902                 // convert to number of nanoseconds
903
while (length < 9) {
904                     nano *= 10;
905                     length++;
906                 }
907                 
908                 // strip fractional second off text
909
if (scan < limit) {
910                     text = text.substring(0, split) + text.substring(scan);
911                 } else {
912                     text = text.substring(0, split);
913                 }
914             }
915             
916             // return timestamp value with nanoseconds
917
Timestamp JavaDoc stamp = new Timestamp JavaDoc(parseDateTime(text));
918             stamp.setNanos(nano);
919             return stamp;
920         }
921     }
922
923     /**
924      * Deserialize time from text. Time values obey the rules of the time
925      * portion of a dataTime value. This method follows standard JiBX
926      * deserializer usage requirements by accepting a <code>null</code> input.
927      *
928      * @param text text to be parsed (may be <code>null</code>)
929      * @return converted time, or <code>null</code> if passed <code>null</code>
930      * input
931      * @throws JiBXException on parse error
932      */

933
934     public static Time JavaDoc deserializeSqlTime(String JavaDoc text)
935         throws JiBXException {
936         if (text == null) {
937             return null;
938         } else {
939             return new Time JavaDoc(parseTime(text, 0, text.length()));
940         }
941     }
942
943     /**
944      * Format year number consistent with W3C XML Schema definitions, using a
945      * minimum of four digits padded with zeros if necessary. A leading minus
946      * sign is included for years prior to 1 C.E.
947      *
948      * @param year number to be formatted
949      * @param buff text formatting buffer
950      */

951
952     protected static void formatYearNumber(long year, StringBuffer JavaDoc buff) {
953         
954         // start with minus sign for dates prior to 1 C.E.
955
if (year <= 0) {
956             buff.append('-');
957             year = -(year-1);
958         }
959         
960         // add padding if needed to bring to length of four
961
if (year < 1000) {
962             buff.append('0');
963             if (year < 100) {
964                 buff.append('0');
965                 if (year < 10) {
966                     buff.append('0');
967                 }
968             }
969         }
970         
971         // finish by converting the actual year number
972
buff.append(year);
973     }
974
975     /**
976      * Format a positive number as two digits. This uses an optional leading
977      * zero digit for values less than ten.
978      *
979      * @param value number to be formatted (<code>0</code> to <code>99</code>)
980      * @param buff text formatting buffer
981      */

982     
983     protected static void formatTwoDigits(int value, StringBuffer JavaDoc buff) {
984         if (value < 10) {
985             buff.append('0');
986         }
987         buff.append(value);
988     }
989
990     /**
991      * Format time in milliseconds to year number. The resulting year number
992      * format is consistent with W3C XML Schema definitions, using a minimum
993      * of four digits padded with zeros if necessary. A leading minus sign is
994      * included for years prior to 1 C.E.
995      *
996      * @param value time in milliseconds to be converted (from 1 C.E.)
997      * @param buff text formatting buffer
998      * @return milliseconds within year (remainder from formatting)
999      */

1000
1001    protected static void formatYear(long value, StringBuffer JavaDoc buff) {
1002        
1003        // find the actual year and month number; this uses a integer arithmetic
1004
// conversion based on Baum, first making the millisecond count
1005
// relative to March 1 of the year 0 C.E., then using simple arithmetic
1006
// operations to compute century, year, and month; it's slightly
1007
// different for pre-C.E. values because of Java's handling of divisions.
1008
long time = value + 306*LMSPERDAY + LMSPERDAY*3/4;
1009        long century = time / MSPERCENTURY; // count of centuries
1010
long adjusted = time + (century - (century/4)) * MSPERDAY;
1011        int year = (int)(adjusted / MSPERAVGYEAR); // year in March 1 terms
1012
if (adjusted < 0) {
1013            year--;
1014        }
1015        long yms = adjusted + LMSPERDAY/4 - (year * 365 + year/4) * LMSPERDAY;
1016        int yday = (int)(yms / LMSPERDAY); // day number in year
1017
int month = (5*yday + 456) / 153; // (biased) month number
1018
if (month > 12) { // convert start of year
1019
year++;
1020        }
1021        
1022        // format year to text
1023
formatYearNumber(year, buff);
1024    }
1025
1026    /**
1027     * Format time in milliseconds to year number and month number. The
1028     * resulting year number format is consistent with W3C XML Schema
1029     * definitions, using a minimum of four digits for the year and exactly
1030     * two digits for the month.
1031     *
1032     * @param value time in milliseconds to be converted (from 1 C.E.)
1033     * @param buff text formatting buffer
1034     * @return number of milliseconds into month
1035     */

1036
1037    protected static long formatYearMonth(long value, StringBuffer JavaDoc buff) {
1038        
1039        // find the actual year and month number; this uses a integer arithmetic
1040
// conversion based on Baum, first making the millisecond count
1041
// relative to March 1 of the year 0 C.E., then using simple arithmetic
1042
// operations to compute century, year, and month; it's slightly
1043
// different for pre-C.E. values because of Java's handling of divisions.
1044
long time = value + 306*LMSPERDAY + LMSPERDAY*3/4;
1045        long century = time / MSPERCENTURY; // count of centuries
1046
long adjusted = time + (century - (century/4)) * MSPERDAY;
1047        int year = (int)(adjusted / MSPERAVGYEAR); // year in March 1 terms
1048
if (adjusted < 0) {
1049            year--;
1050        }
1051        long yms = adjusted + LMSPERDAY/4 - (year * 365 + year/4) * LMSPERDAY;
1052        int yday = (int)(yms / LMSPERDAY); // day number in year
1053
if (yday == 0) { // special for negative
1054
boolean bce = year < 0;
1055            if (bce) {
1056                year--;
1057            }
1058            int dcnt = year % 4 == 0 ? 366 : 365;
1059            if (!bce) {
1060                year--;
1061            }
1062            yms += dcnt * LMSPERDAY;
1063            yday += dcnt;
1064        }
1065        int month = (5*yday + 456) / 153; // (biased) month number
1066
long rem = yms - BIAS_MONTHMS[month] - LMSPERDAY; // ms into month
1067
if (month > 12) { // convert start of year
1068
year++;
1069            month -= 12;
1070        }
1071        
1072        // format year and month as text
1073
formatYearNumber(year, buff);
1074        buff.append('-');
1075        formatTwoDigits(month, buff);
1076        
1077        // return extra milliseconds into month
1078
return rem;
1079    }
1080
1081    /**
1082     * Format time in milliseconds to year number, month number, and day
1083     * number. The resulting year number format is consistent with W3C XML
1084     * Schema definitions, using a minimum of four digits for the year and
1085     * exactly two digits each for the month and day.
1086     *
1087     * @param value time in milliseconds to be converted (from 1 C.E.)
1088     * @param buff text formatting buffer
1089     * @return number of milliseconds into day
1090     */

1091
1092    protected static int formatYearMonthDay(long value, StringBuffer JavaDoc buff) {
1093        
1094        // convert year and month
1095
long extra = formatYearMonth(value, buff);
1096        
1097        // append the day of month
1098
int day = (int)(extra / MSPERDAY) + 1;
1099        buff.append('-');
1100        formatTwoDigits(day, buff);
1101        
1102        // return excess of milliseconds into day
1103
return (int)(extra % MSPERDAY);
1104    }
1105
1106    /**
1107     * Serialize time to general gYear text. Date values are formatted in
1108     * W3C XML Schema standard format as CCYY, with optional
1109     * leading sign included if necessary.
1110     *
1111     * @param time time to be converted, as milliseconds from January 1, 1970
1112     * @return converted gYear text
1113     * @throws JiBXException on conversion error
1114     */

1115
1116    public static String JavaDoc serializeYear(long time) throws JiBXException {
1117        StringBuffer JavaDoc buff = new StringBuffer JavaDoc(6);
1118        formatYear(time + TIME_BASE, buff);
1119        return buff.toString();
1120    }
1121
1122    /**
1123     * Serialize date to general gYear text. Date values are formatted in
1124     * W3C XML Schema standard format as CCYY, with optional
1125     * leading sign included if necessary.
1126     *
1127     * @param date date to be converted
1128     * @return converted gYear text
1129     * @throws JiBXException on conversion error
1130     */

1131
1132    public static String JavaDoc serializeYear(Date date) throws JiBXException {
1133        return serializeYear(date.getTime());
1134    }
1135
1136    /**
1137     * Serialize time to general gYearMonth text. Date values are formatted in
1138     * W3C XML Schema standard format as CCYY-MM, with optional
1139     * leading sign included if necessary.
1140     *
1141     * @param time time to be converted, as milliseconds from January 1, 1970
1142     * @return converted gYearMonth text
1143     * @throws JiBXException on conversion error
1144     */

1145
1146    public static String JavaDoc serializeYearMonth(long time) throws JiBXException {
1147        StringBuffer JavaDoc buff = new StringBuffer JavaDoc(12);
1148        formatYearMonth(time + TIME_BASE, buff);
1149        return buff.toString();
1150    }
1151
1152    /**
1153     * Serialize date to general gYearMonth text. Date values are formatted in
1154     * W3C XML Schema standard format as CCYY-MM, with optional
1155     * leading sign included if necessary.
1156     *
1157     * @param date date to be converted
1158     * @return converted gYearMonth text
1159     * @throws JiBXException on conversion error
1160     */

1161
1162    public static String JavaDoc serializeYearMonth(Date date) throws JiBXException {
1163        return serializeYearMonth(date.getTime());
1164    }
1165
1166    /**
1167     * Serialize time to general date text. Date values are formatted in
1168     * W3C XML Schema standard format as CCYY-MM-DD, with optional
1169     * leading sign included if necessary.
1170     *
1171     * @param time time to be converted, as milliseconds from January 1, 1970
1172     * @return converted date text
1173     * @throws JiBXException on conversion error
1174     */

1175
1176    public static String JavaDoc serializeDate(long time) throws JiBXException {
1177        StringBuffer JavaDoc buff = new StringBuffer JavaDoc(12);
1178        formatYearMonthDay(time + TIME_BASE, buff);
1179        return buff.toString();
1180    }
1181
1182    /**
1183     * Serialize date to general date text. Date values are formatted in
1184     * W3C XML Schema standard format as CCYY-MM-DD, with optional
1185     * leading sign included if necessary.
1186     *
1187     * @param date date to be converted
1188     * @return converted date text
1189     * @throws JiBXException on conversion error
1190     */

1191
1192    public static String JavaDoc serializeDate(Date date) throws JiBXException {
1193        return serializeDate(date.getTime());
1194    }
1195
1196    /**
1197     * Serialize SQL date to general date text. Date values are formatted in
1198     * W3C XML Schema standard format as CCYY-MM-DD, with optional
1199     * leading sign included if necessary.
1200     *
1201     * @param date date to be converted
1202     * @return converted date text
1203     * @throws JiBXException on conversion error
1204     */

1205
1206    public static String JavaDoc serializeSqlDate(java.sql.Date JavaDoc date)
1207        throws JiBXException {
1208        
1209        // convert time assuming maximum daylight time difference of two hours
1210
long time = date.getTime();
1211        time -= TimeZone.getDefault().getRawOffset() - MSPERHOUR*2;
1212        return serializeDate(time);
1213    }
1214
1215    /**
1216     * Serialize time to general time text in buffer. Time values are formatted
1217     * in W3C XML Schema standard format as hh:mm:ss, with optional trailing
1218     * seconds decimal, as necessary. This form uses a supplied buffer to
1219     * support flexible use, including with dateTime combination values.
1220     *
1221     * @param time time to be converted, as milliseconds in day
1222     * @param buff buffer for appending time text
1223     * @throws JiBXException on conversion error
1224     */

1225
1226    public static void serializeTime(int time, StringBuffer JavaDoc buff)
1227        throws JiBXException {
1228        
1229        // append the hour, minute, and second
1230
formatTwoDigits(time/MSPERHOUR, buff);
1231        time = time % MSPERHOUR;
1232        buff.append(':');
1233        formatTwoDigits(time/MSPERMINUTE, buff);
1234        time = time % MSPERMINUTE;
1235        buff.append(':');
1236        formatTwoDigits(time/1000, buff);
1237        time = time % 1000;
1238        
1239        // check if decimals needed on second
1240
if (time > 0) {
1241            buff.append('.');
1242            buff.append(time / 100);
1243            time = time % 100;
1244            if (time > 0) {
1245                buff.append(time / 10);
1246                time = time % 10;
1247                if (time > 0) {
1248                    buff.append(time);
1249                }
1250            }
1251        }
1252    }
1253
1254    /**
1255     * Serialize time to general dateTime text. Date values are formatted in
1256     * W3C XML Schema standard format as CCYY-MM-DDThh:mm:ss, with optional
1257     * leading sign and trailing seconds decimal, as necessary.
1258     *
1259     * @param time time to be converted, as milliseconds from January 1, 1970
1260     * @param zone flag for trailing 'Z' to be appended to indicate UTC
1261     * @return converted dateTime text
1262     * @throws JiBXException on conversion error
1263     */

1264
1265    public static String JavaDoc serializeDateTime(long time, boolean zone)
1266        throws JiBXException {
1267        
1268        // start with the year, month, and day
1269
StringBuffer JavaDoc buff = new StringBuffer JavaDoc(25);
1270        int extra = formatYearMonthDay(time + TIME_BASE, buff);
1271        
1272        // append the time for full form
1273
buff.append('T');
1274        serializeTime(extra, buff);
1275        
1276        // return full text with optional trailing zone indicator
1277
if (zone) {
1278            buff.append('Z');
1279        }
1280        return buff.toString();
1281    }
1282
1283    /**
1284     * Serialize time to general dateTime text. This method is provided for
1285     * backward compatibility. It generates the dateTime text without the
1286     * trailing 'Z' to indicate UTC.
1287     *
1288     * @param time time to be converted, as milliseconds from January 1, 1970
1289     * @return converted dateTime text
1290     * @throws JiBXException on conversion error
1291     */

1292
1293    public static String JavaDoc serializeDateTime(long time) throws JiBXException {
1294        return serializeDateTime(time, false);
1295    }
1296
1297    /**
1298     * Serialize date to general dateTime text. Date values are formatted in
1299     * W3C XML Schema standard format as CCYY-MM-DDThh:mm:ss, with optional
1300     * leading sign and trailing seconds decimal, as necessary.
1301     *
1302     * @param date date to be converted
1303     * @return converted dateTime text
1304     * @throws JiBXException on conversion error
1305     */

1306
1307    public static String JavaDoc serializeDateTime(Date date) throws JiBXException {
1308        return serializeDateTime(date.getTime(), false);
1309    }
1310
1311    /**
1312     * Serialize timestamp to general dateTime text. Timestamp values are
1313     * represented in the same way as regular dates, but allow more precision in
1314     * the fractional second value (down to nanoseconds).
1315     *
1316     * @param stamp timestamp to be converted
1317     * @return converted dateTime text
1318     * @throws JiBXException on conversion error
1319     */

1320
1321    public static String JavaDoc serializeTimestamp(Timestamp JavaDoc stamp)
1322        throws JiBXException {
1323        
1324        // check for nanosecond value to be included
1325
int nano = stamp.getNanos();
1326        if (nano > 0) {
1327            
1328            // convert the number of nanoseconds to text
1329
String JavaDoc value = serializeInt(nano);
1330            
1331            // pad with leading zeros if less than 9 digits
1332
StringBuffer JavaDoc digits = new StringBuffer JavaDoc(9);
1333            if (value.length() < 9) {
1334                int lead = 9 - value.length();
1335                for (int i = 0; i < lead; i++) {
1336                    digits.append('0');
1337                }
1338            }
1339            digits.append(value);
1340            
1341            // strip trailing zeros from value
1342
int last = 9;
1343            while (--last > 0) {
1344                if (digits.charAt(last) != '0') {
1345                    break;
1346                }
1347            }
1348            digits.setLength(last);
1349            
1350            // finish by appending to time with decimal separator
1351
return serializeDateTime(stamp.getTime(), false) + '.' +
1352                digits + 'Z';
1353            
1354        } else {
1355            return serializeDateTime(stamp.getTime(), true);
1356        }
1357    }
1358
1359    /**
1360     * Serialize time to standard text. Time values are formatted in W3C XML
1361     * Schema standard format as hh:mm:ss, with optional trailing seconds
1362     * decimal, as necessary. The standard conversion does not append a time
1363     * zone indication.
1364     *
1365     * @param time time to be converted
1366     * @return converted time text
1367     * @throws JiBXException on conversion error
1368     */

1369
1370    public static String JavaDoc serializeSqlTime(Time JavaDoc time) throws JiBXException {
1371        StringBuffer JavaDoc buff = new StringBuffer JavaDoc(12);
1372        serializeTime((int)time.getTime(), buff);
1373        return buff.toString();
1374    }
1375    
1376    /**
1377     * General object comparison method. Don't know why Sun hasn't seen fit to
1378     * include this somewhere, but at least it's easy to write (over and over
1379     * again).
1380     *
1381     * @param a first object to be compared
1382     * @param b second object to be compared
1383     * @return <code>true</code> if both objects are <code>null</code>, or if
1384     * <code>a.equals(b)</code>; <code>false</code> otherwise
1385     */

1386    
1387    public static boolean isEqual(Object JavaDoc a, Object JavaDoc b) {
1388        return (a == null) ? b == null : a.equals(b);
1389    }
1390
1391    /**
1392     * Find text value in enumeration. This first does a binary search through
1393     * an array of allowed text matches. If a separate array of corresponding
1394     * values is supplied, the value at the matched position is returned;
1395     * otherwise the match index is returned directly.
1396     *
1397     * @param target text to be found in enumeration
1398     * @param enums ordered array of texts included in enumeration
1399     * @param vals array of values to be returned for corresponding text match
1400     * positions (position returned directly if this is <code>null</code>)
1401     * @return enumeration value for target text
1402     * @throws JiBXException if target text not found in enumeration
1403     */

1404
1405    public static int enumValue(String JavaDoc target, String JavaDoc[] enums, int[] vals)
1406        throws JiBXException {
1407        int base = 0;
1408        int limit = enums.length - 1;
1409        while (base <= limit) {
1410            int cur = (base + limit) >> 1;
1411            int diff = target.compareTo(enums[cur]);
1412            if (diff < 0) {
1413                limit = cur - 1;
1414            } else if (diff > 0) {
1415                base = cur + 1;
1416            } else if (vals != null) {
1417                return vals[cur];
1418            } else {
1419                return cur;
1420            }
1421        }
1422        throw new JiBXException("Target value \"" + target +
1423            "\" not found in enumeration");
1424    }
1425
1426    /**
1427     * Decode a chunk of data from base64 encoding. The length of a chunk is
1428     * always 4 characters in the base64 representation, but may be 1, 2, or 3
1429     * bytes of data, as determined by whether there are any pad characters at
1430     * the end of the base64 representation
1431     *
1432     * @param base starting offset within base64 character array
1433     * @param chrs character array for base64 text representation
1434     * @param fill starting offset within byte data array
1435     * @param byts byte data array
1436     * @return number of decoded bytes
1437     * @throws JiBXException if invalid character in base64 representation
1438     */

1439
1440    private static int decodeChunk(int base, char[] chrs, int fill,
1441        byte[] byts) throws JiBXException {
1442        
1443        // find the byte count to be decoded
1444
int length = 3;
1445        if (chrs[base+3] == PAD_CHAR) {
1446            length = 2;
1447            if (chrs[base+2] == PAD_CHAR) {
1448                length = 1;
1449            }
1450        }
1451        
1452        // get 6-bit values
1453
int v0 = s_base64Values[chrs[base+0]];
1454        int v1 = s_base64Values[chrs[base+1]];
1455        int v2 = s_base64Values[chrs[base+2]];
1456        int v3 = s_base64Values[chrs[base+3]];
1457        
1458        // convert and store bytes of data
1459
switch (length) {
1460            case 3:
1461                byts[fill+2] = (byte)(v2 << 6 | v3);
1462            case 2:
1463                byts[fill+1] = (byte)(v1 << 4 | v2 >> 2);
1464            case 1:
1465                byts[fill] = (byte)(v0 << 2 | v1 >> 4);
1466                break;
1467        }
1468        return length;
1469    }
1470
1471    /**
1472     * Parse base64 data from text. This converts the base64 data into a byte
1473     * array of the appopriate length. In keeping with the recommendations,
1474     *
1475     * @param text text to be parsed (may include extra characters)
1476     * @return byte array of data
1477     * @throws JiBXException if invalid character in base64 representation
1478     */

1479    
1480    public static byte[] parseBase64(String JavaDoc text) throws JiBXException {
1481        
1482        // convert raw text to base64 character array
1483
char[] chrs = new char[text.length()];
1484        int length = 0;
1485        for (int i = 0; i < text.length(); i++) {
1486            char chr = text.charAt(i);
1487            if (chr < 128 && s_base64Values[chr] >= 0) {
1488                chrs[length++] = chr;
1489            }
1490        }
1491        
1492        // check the text length
1493
if (length % 4 != 0) {
1494            throw new JiBXException
1495                ("Text length for base64 must be a multiple of 4");
1496        } else if (length == 0) {
1497            return new byte[0];
1498        }
1499        
1500        // find corresponding byte count for data
1501
int blength = length / 4 * 3;
1502        if (chrs[length-1] == PAD_CHAR) {
1503            blength--;
1504            if (chrs[length-2] == PAD_CHAR) {
1505                blength--;
1506            }
1507        }
1508        
1509        // convert text to actual bytes of data
1510
byte[] byts = new byte[blength];
1511        int fill = 0;
1512        for (int i = 0; i < length; i += 4) {
1513            fill += decodeChunk(i, chrs, fill, byts);
1514        }
1515        if (fill != blength) {
1516            throw new JiBXException
1517                ("Embedded padding characters in byte64 text");
1518        }
1519        return byts;
1520    }
1521
1522    /**
1523     * Parse base64 data from text. This converts the base64 data into a byte
1524     * array of the appopriate length. In keeping with the recommendations,
1525     *
1526     * @param text text to be parsed (may be null, or include extra characters)
1527     * @return byte array of data
1528     * @throws JiBXException if invalid character in base64 representation
1529     */

1530
1531    public static byte[] deserializeBase64(String JavaDoc text) throws JiBXException {
1532        if (text == null) {
1533            return null;
1534        } else {
1535            return parseBase64(text);
1536        }
1537    }
1538
1539    /**
1540     * Encode a chunk of data to base64 encoding. Converts the next three bytes
1541     * of data into four characters of text representation, using padding at the
1542     * end of less than three bytes of data remain.
1543     *
1544     * @param base starting offset within byte array
1545     * @param byts byte data array
1546     * @param buff buffer for encoded text
1547     */

1548    
1549    public static void encodeChunk(int base, byte[] byts, StringBuffer JavaDoc buff) {
1550        
1551        // get actual byte data length to be encoded
1552
int length = 3;
1553        if (base + length > byts.length) {
1554            length = byts.length - base;
1555        }
1556        
1557        // convert up to three bytes of data to four characters of text
1558
int b0 = byts[base];
1559        int value = (b0 >> 2) & 0x3F;
1560        buff.append(s_base64Chars[value]);
1561        if (length > 1) {
1562            int b1 = byts[base+1];
1563            value = ((b0 & 3) << 4) + ((b1 >> 4) & 0x0F);
1564            buff.append(s_base64Chars[value]);
1565            if (length > 2) {
1566                int b2 = byts[base+2];
1567                value = ((b1 & 0x0F) << 2) + ((b2 >> 6) & 3);
1568                buff.append(s_base64Chars[value]);
1569                value = b2 & 0x3F;
1570                buff.append(s_base64Chars[value]);
1571            } else {
1572                value = (b1 & 0x0F) << 2;
1573                buff.append(s_base64Chars[value]);
1574                buff.append(PAD_CHAR);
1575            }
1576        } else {
1577            value = (b0 & 3) << 4;
1578            buff.append(s_base64Chars[value]);
1579            buff.append(PAD_CHAR);
1580            buff.append(PAD_CHAR);
1581        }
1582    }
1583
1584    /**
1585     * Serialize byte array to base64 text. In keeping with the specification,
1586     * this adds a line break every 76 characters in the encoded representation.
1587     *
1588     * @param byts byte data array
1589     * @return base64 encoded text
1590     */

1591    
1592    public static String JavaDoc serializeBase64(byte[] byts) {
1593        StringBuffer JavaDoc buff = new StringBuffer JavaDoc((byts.length + 2) / 3 * 4);
1594        for (int i = 0; i < byts.length; i += 3) {
1595            encodeChunk(i, byts, buff);
1596            if (i > 0 && i % 57 == 0 && (i + 3) < byts.length) {
1597                buff.append("\r\n");
1598            }
1599        }
1600        return buff.toString();
1601    }
1602
1603    /**
1604     * Factory method to create a <code>java.util.ArrayList</code> as the
1605     * implementation of a <code>java.util.List</code>.
1606     *
1607     * @return new <code>java.util.ArrayList</code>
1608     */

1609    
1610    public static List arrayListFactory() {
1611        return new ArrayList();
1612    }
1613}
1614
Popular Tags