1 /* 2 * reserved comment block 3 * DO NOT REMOVE OR ALTER! 4 */ 5 /* 6 * Licensed to the Apache Software Foundation (ASF) under one or more 7 * contributor license agreements. See the NOTICE file distributed with 8 * this work for additional information regarding copyright ownership. 9 * The ASF licenses this file to You under the Apache License, Version 2.0 10 * (the "License"); you may not use this file except in compliance with 11 * the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, software 16 * distributed under the License is distributed on an "AS IS" BASIS, 17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 * See the License for the specific language governing permissions and 19 * limitations under the License. 20 */ 21 22 package com.sun.org.apache.xerces.internal.impl.dv.xs; 23 24 import java.math.BigDecimal; 25 26 import javax.xml.datatype.DatatypeFactory; 27 import javax.xml.datatype.Duration; 28 import javax.xml.datatype.XMLGregorianCalendar; 29 30 import com.sun.org.apache.xerces.internal.impl.Constants; 31 import com.sun.org.apache.xerces.internal.jaxp.datatype.DatatypeFactoryImpl; 32 import com.sun.org.apache.xerces.internal.xs.datatypes.XSDateTime; 33 34 /** 35 * This is the base class of all date/time datatype validators. 36 * It implements common code for parsing, validating and comparing datatypes. 37 * Classes that extend this class, must implement parse() method. 38 * 39 * REVISIT: There are many instance variables, which would cause problems 40 * when we support grammar caching. A grammar is possibly used by 41 * two parser instances at the same time, then the same simple type 42 * decl object can be used to validate two strings at the same time. 43 * -SG 44 * 45 * @xerces.internal 46 * 47 * @author Elena Litani 48 * @author Len Berman 49 * @author Gopal Sharma, SUN Microsystems Inc. 50 * 51 */ 52 public abstract class AbstractDateTimeDV extends TypeValidator { 53 54 //debugging 55 private static final boolean DEBUG = false; 56 //define shared variables for date/time 57 //define constants to be used in assigning default values for 58 //all date/time excluding duration 59 protected final static int YEAR = 2000; 60 protected final static int MONTH = 01; 61 protected final static int DAY = 01; 62 protected static final DatatypeFactory datatypeFactory = new DatatypeFactoryImpl(); 63 64 @Override getAllowedFacets()65 public short getAllowedFacets() { 66 return (XSSimpleTypeDecl.FACET_PATTERN | XSSimpleTypeDecl.FACET_WHITESPACE | XSSimpleTypeDecl.FACET_ENUMERATION | XSSimpleTypeDecl.FACET_MAXINCLUSIVE | XSSimpleTypeDecl.FACET_MININCLUSIVE | XSSimpleTypeDecl.FACET_MAXEXCLUSIVE | XSSimpleTypeDecl.FACET_MINEXCLUSIVE); 67 }//getAllowedFacets() 68 69 // distinguishes between identity and equality for date/time values 70 // ie: two values representing the same "moment in time" but with different 71 // remembered timezones are now equal but not identical. 72 @Override isIdentical(Object value1, Object value2)73 public boolean isIdentical(Object value1, Object value2) { 74 if (!(value1 instanceof DateTimeData) || !(value2 instanceof DateTimeData)) { 75 return false; 76 } 77 78 DateTimeData v1 = (DateTimeData) value1; 79 DateTimeData v2 = (DateTimeData) value2; 80 81 // original timezones must be the same in addition to date/time values 82 // being 'equal' 83 if ((v1.timezoneHr == v2.timezoneHr) && (v1.timezoneMin == v2.timezoneMin)) { 84 return v1.equals(v2); 85 } 86 87 return false; 88 }//isIdentical() 89 90 // the parameters are in compiled form (from getActualValue) 91 @Override compare(Object value1, Object value2)92 public int compare(Object value1, Object value2) { 93 return compareDates(((DateTimeData) value1), 94 ((DateTimeData) value2), true); 95 }//compare() 96 97 /** 98 * Compare algorithm described in dateDime (3.2.7). Duration datatype 99 * overwrites this method 100 * 101 * @param date1 normalized date representation of the first value 102 * @param date2 normalized date representation of the second value 103 * @param strict 104 * @return less, greater, less_equal, greater_equal, equal 105 */ compareDates(DateTimeData date1, DateTimeData date2, boolean strict)106 protected short compareDates(DateTimeData date1, DateTimeData date2, boolean strict) { 107 if (date1.utc == date2.utc) { 108 return compareOrder(date1, date2); 109 } 110 short c1, c2; 111 112 DateTimeData tempDate = new DateTimeData(null, this); 113 114 if (date1.utc == 'Z') { 115 116 //compare date1<=date1<=(date2 with time zone -14) 117 // 118 cloneDate(date2, tempDate); //clones date1 value to global temporary storage: fTempDate 119 tempDate.timezoneHr = 14; 120 tempDate.timezoneMin = 0; 121 tempDate.utc = '+'; 122 normalize(tempDate); 123 c1 = compareOrder(date1, tempDate); 124 if (c1 == LESS_THAN) { 125 return c1; 126 } 127 128 //compare date1>=(date2 with time zone +14) 129 // 130 cloneDate(date2, tempDate); //clones date1 value to global temporary storage: tempDate 131 tempDate.timezoneHr = -14; 132 tempDate.timezoneMin = 0; 133 tempDate.utc = '-'; 134 normalize(tempDate); 135 c2 = compareOrder(date1, tempDate); 136 if (c2 == GREATER_THAN) { 137 return c2; 138 } 139 140 return INDETERMINATE; 141 } else if (date2.utc == 'Z') { 142 143 //compare (date1 with time zone -14)<=date2 144 // 145 cloneDate(date1, tempDate); //clones date1 value to global temporary storage: tempDate 146 tempDate.timezoneHr = -14; 147 tempDate.timezoneMin = 0; 148 tempDate.utc = '-'; 149 if (DEBUG) { 150 System.out.println("tempDate=" + dateToString(tempDate)); 151 } 152 normalize(tempDate); 153 c1 = compareOrder(tempDate, date2); 154 if (DEBUG) { 155 System.out.println("date=" + dateToString(date2)); 156 System.out.println("tempDate=" + dateToString(tempDate)); 157 } 158 if (c1 == LESS_THAN) { 159 return c1; 160 } 161 162 //compare (date1 with time zone +14)<=date2 163 // 164 cloneDate(date1, tempDate); //clones date1 value to global temporary storage: tempDate 165 tempDate.timezoneHr = 14; 166 tempDate.timezoneMin = 0; 167 tempDate.utc = '+'; 168 normalize(tempDate); 169 c2 = compareOrder(tempDate, date2); 170 if (DEBUG) { 171 System.out.println("tempDate=" + dateToString(tempDate)); 172 } 173 if (c2 == GREATER_THAN) { 174 return c2; 175 } 176 177 return INDETERMINATE; 178 } 179 return INDETERMINATE; 180 181 } 182 183 /** 184 * Given normalized values, determines order-relation between give date/time 185 * objects. 186 * 187 * @param date1 date/time object 188 * @param date2 date/time object 189 * @return 0 if date1 and date2 are equal, a value less than 0 if date1 is 190 * less than date2, a value greater than 0 if date1 is greater than date2 191 */ compareOrder(DateTimeData date1, DateTimeData date2)192 protected short compareOrder(DateTimeData date1, DateTimeData date2) { 193 if (date1.position < 1) { 194 if (date1.year < date2.year) { 195 return -1; 196 } 197 if (date1.year > date2.year) { 198 return 1; 199 } 200 } 201 if (date1.position < 2) { 202 if (date1.month < date2.month) { 203 return -1; 204 } 205 if (date1.month > date2.month) { 206 return 1; 207 } 208 } 209 if (date1.day < date2.day) { 210 return -1; 211 } 212 if (date1.day > date2.day) { 213 return 1; 214 } 215 if (date1.hour < date2.hour) { 216 return -1; 217 } 218 if (date1.hour > date2.hour) { 219 return 1; 220 } 221 if (date1.minute < date2.minute) { 222 return -1; 223 } 224 if (date1.minute > date2.minute) { 225 return 1; 226 } 227 if (date1.second < date2.second) { 228 return -1; 229 } 230 if (date1.second > date2.second) { 231 return 1; 232 } 233 if (date1.utc < date2.utc) { 234 return -1; 235 } 236 if (date1.utc > date2.utc) { 237 return 1; 238 } 239 return 0; 240 } 241 242 /** 243 * Parses time hh:mm:ss.sss and time zone if any 244 * 245 * @param start 246 * @param end 247 * @param data 248 * @exception RuntimeException 249 */ getTime(String buffer, int start, int end, DateTimeData data)250 protected void getTime(String buffer, int start, int end, DateTimeData data) throws RuntimeException { 251 252 int stop = start + 2; 253 254 //get hours (hh) 255 data.hour = parseInt(buffer, start, stop); 256 257 //get minutes (mm) 258 259 if (buffer.charAt(stop++) != ':') { 260 throw new RuntimeException("Error in parsing time zone"); 261 } 262 start = stop; 263 stop = stop + 2; 264 data.minute = parseInt(buffer, start, stop); 265 266 //get seconds (ss) 267 if (buffer.charAt(stop++) != ':') { 268 throw new RuntimeException("Error in parsing time zone"); 269 } 270 271 //find UTC sign if any 272 int sign = findUTCSign(buffer, start, end); 273 274 //get seconds (ms) 275 start = stop; 276 stop = sign < 0 ? end : sign; 277 data.second = parseSecond(buffer, start, stop); 278 279 //parse UTC time zone (hh:mm) 280 if (sign > 0) { 281 getTimeZone(buffer, data, sign, end); 282 } 283 } 284 285 /** 286 * Parses date CCYY-MM-DD 287 * 288 * @param buffer 289 * @param start start position 290 * @param end end position 291 * @param date 292 * @exception RuntimeException 293 */ 294 protected int getDate(String buffer, int start, int end, DateTimeData date) throws RuntimeException { 295 296 start = getYearMonth(buffer, start, end, date); 297 298 if (buffer.charAt(start++) != '-') { 299 throw new RuntimeException("CCYY-MM must be followed by '-' sign"); 300 } 301 int stop = start + 2; 302 date.day = parseInt(buffer, start, stop); 303 return stop; 304 } 305 306 /** 307 * Parses date CCYY-MM 308 * 309 * @param buffer 310 * @param start start position 311 * @param end end position 312 * @param date 313 * @exception RuntimeException 314 */ 315 protected int getYearMonth(String buffer, int start, int end, DateTimeData date) throws RuntimeException { 316 317 if (buffer.charAt(0) == '-') { 318 // REVISIT: date starts with preceding '-' sign 319 // do we have to do anything with it? 320 // 321 start++; 322 } 323 int i = indexOf(buffer, start, end, '-'); 324 if (i == -1) { 325 throw new RuntimeException("Year separator is missing or misplaced"); 326 } 327 int length = i - start; 328 if (length < 4) { 329 throw new RuntimeException("Year must have 'CCYY' format"); 330 } else if (length > 4 && buffer.charAt(start) == '0') { 331 throw new RuntimeException("Leading zeros are required if the year value would otherwise have fewer than four digits; otherwise they are forbidden"); 332 } 333 date.year = parseIntYear(buffer, i); 334 if (buffer.charAt(i) != '-') { 335 throw new RuntimeException("CCYY must be followed by '-' sign"); 336 } 337 start = ++i; 338 i = start + 2; 339 date.month = parseInt(buffer, start, i); 340 return i; //fStart points right after the MONTH 341 } 342 343 /** 344 * Shared code from Date and YearMonth datatypes. Finds if time zone sign is 345 * present 346 * 347 * @param end 348 * @param date 349 * @exception RuntimeException 350 */ 351 protected void parseTimeZone(String buffer, int start, int end, DateTimeData date) throws RuntimeException { 352 353 //fStart points right after the date 354 355 if (start < end) { 356 if (!isNextCharUTCSign(buffer, start, end)) { 357 throw new RuntimeException("Error in month parsing"); 358 } else { 359 getTimeZone(buffer, date, start, end); 360 } 361 } 362 } 363 364 /** 365 * Parses time zone: 'Z' or {+,-} followed by hh:mm 366 * 367 * @param data 368 * @param sign 369 * @exception RuntimeException 370 */ 371 protected void getTimeZone(String buffer, DateTimeData data, int sign, int end) throws RuntimeException { 372 data.utc = buffer.charAt(sign); 373 374 if (buffer.charAt(sign) == 'Z') { 375 if (end > (++sign)) { 376 throw new RuntimeException("Error in parsing time zone"); 377 } 378 return; 379 } 380 if (sign <= (end - 6)) { 381 382 int negate = buffer.charAt(sign) == '-' ? -1 : 1; 383 //parse hr 384 int stop = ++sign + 2; 385 data.timezoneHr = negate * parseInt(buffer, sign, stop); 386 if (buffer.charAt(stop++) != ':') { 387 throw new RuntimeException("Error in parsing time zone"); 388 } 389 390 //parse min 391 data.timezoneMin = negate * parseInt(buffer, stop, stop + 2); 392 393 if (stop + 2 != end) { 394 throw new RuntimeException("Error in parsing time zone"); 395 } 396 if (data.timezoneHr != 0 || data.timezoneMin != 0) { 397 data.normalized = false; 398 } 399 } else { 400 throw new RuntimeException("Error in parsing time zone"); 401 } 402 if (DEBUG) { 403 System.out.println("time[hh]=" + data.timezoneHr + " time[mm]=" + data.timezoneMin); 404 } 405 } 406 407 /** 408 * Computes index of given char within StringBuffer 409 * 410 * @param start 411 * @param end 412 * @param ch character to look for in StringBuffer 413 * @return index of ch within StringBuffer 414 */ 415 protected int indexOf(String buffer, int start, int end, char ch) { 416 for (int i = start; i < end; i++) { 417 if (buffer.charAt(i) == ch) { 418 return i; 419 } 420 } 421 return -1; 422 } 423 424 /** 425 * Validates given date/time object accoring to W3C PR Schema [D.1 ISO 8601 426 * Conventions] 427 * 428 * @param data 429 */ 430 protected void validateDateTime(DateTimeData data) { 431 432 //REVISIT: should we throw an exception for not valid dates 433 // or reporting an error message should be sufficient? 434 435 /** 436 * XML Schema 1.1 - RQ-123: Allow year 0000 in date related types. 437 */ 438 if (!Constants.SCHEMA_1_1_SUPPORT && data.year == 0) { 439 throw new RuntimeException("The year \"0000\" is an illegal year value"); 440 441 } 442 443 if (data.month < 1 || data.month > 12) { 444 throw new RuntimeException("The month must have values 1 to 12"); 445 446 } 447 448 //validate days 449 if (data.day > maxDayInMonthFor(data.year, data.month) || data.day < 1) { 450 throw new RuntimeException("The day must have values 1 to 31"); 451 } 452 453 //validate hours 454 if (data.hour > 23 || data.hour < 0) { 455 if (data.hour == 24 && data.minute == 0 && data.second == 0) { 456 data.hour = 0; 457 if (++data.day > maxDayInMonthFor(data.year, data.month)) { 458 data.day = 1; 459 if (++data.month > 12) { 460 data.month = 1; 461 if (Constants.SCHEMA_1_1_SUPPORT) { 462 ++data.year; 463 } else if (++data.year == 0) { 464 data.year = 1; 465 } 466 } 467 } 468 } else { 469 throw new RuntimeException("Hour must have values 0-23, unless 24:00:00"); 470 } 471 } 472 473 //validate 474 if (data.minute > 59 || data.minute < 0) { 475 throw new RuntimeException("Minute must have values 0-59"); 476 } 477 478 //validate 479 if (data.second >= 60 || data.second < 0) { 480 throw new RuntimeException("Second must have values 0-59"); 481 482 } 483 484 //validate 485 if (data.timezoneHr > 14 || data.timezoneHr < -14) { 486 throw new RuntimeException("Time zone should have range -14:00 to +14:00"); 487 } else { 488 if ((data.timezoneHr == 14 || data.timezoneHr == -14) && data.timezoneMin != 0) { 489 throw new RuntimeException("Time zone should have range -14:00 to +14:00"); 490 } else if (data.timezoneMin > 59 || data.timezoneMin < -59) { 491 throw new RuntimeException("Minute must have values 0-59"); 492 } 493 } 494 495 } 496 497 /** 498 * Return index of UTC char: 'Z', '+', '-' 499 * 500 * @param start 501 * @param end 502 * @return index of the UTC character that was found 503 */ 504 protected int findUTCSign(String buffer, int start, int end) { 505 int c; 506 for (int i = start; i < end; i++) { 507 c = buffer.charAt(i); 508 if (c == 'Z' || c == '+' || c == '-') { 509 return i; 510 } 511 512 } 513 return -1; 514 } 515 516 /** 517 * Returns 518 * <code>true</code> if the character at start is 'Z', '+' or '-'. 519 */ 520 protected final boolean isNextCharUTCSign(String buffer, int start, int end) { 521 if (start < end) { 522 char c = buffer.charAt(start); 523 return (c == 'Z' || c == '+' || c == '-'); 524 } 525 return false; 526 } 527 528 /** 529 * Given start and end position, parses string value 530 * 531 * @param buffer string to parse 532 * @param start start position 533 * @param end end position 534 * @return return integer representation of characters 535 */ 536 protected int parseInt(String buffer, int start, int end) 537 throws NumberFormatException { 538 //REVISIT: more testing on this parsing needs to be done. 539 int radix = 10; 540 int result = 0; 541 int digit = 0; 542 int limit = -Integer.MAX_VALUE; 543 int multmin = limit / radix; 544 int i = start; 545 do { 546 digit = getDigit(buffer.charAt(i)); 547 if (digit < 0) { 548 throw new NumberFormatException("'" + buffer + "' has wrong format"); 549 } 550 if (result < multmin) { 551 throw new NumberFormatException("'" + buffer + "' has wrong format"); 552 } 553 result *= radix; 554 if (result < limit + digit) { 555 throw new NumberFormatException("'" + buffer + "' has wrong format"); 556 } 557 result -= digit; 558 559 } while (++i < end); 560 return -result; 561 } 562 563 // parse Year differently to support negative value. 564 protected int parseIntYear(String buffer, int end) { 565 int radix = 10; 566 int result = 0; 567 boolean negative = false; 568 int i = 0; 569 int limit; 570 int multmin; 571 int digit = 0; 572 573 if (buffer.charAt(0) == '-') { 574 negative = true; 575 limit = Integer.MIN_VALUE; 576 i++; 577 578 } else { 579 limit = -Integer.MAX_VALUE; 580 } 581 multmin = limit / radix; 582 while (i < end) { 583 digit = getDigit(buffer.charAt(i++)); 584 if (digit < 0) { 585 throw new NumberFormatException("'" + buffer + "' has wrong format"); 586 } 587 if (result < multmin) { 588 throw new NumberFormatException("'" + buffer + "' has wrong format"); 589 } 590 result *= radix; 591 if (result < limit + digit) { 592 throw new NumberFormatException("'" + buffer + "' has wrong format"); 593 } 594 result -= digit; 595 } 596 597 if (negative) { 598 if (i > 1) { 599 return result; 600 } else { 601 throw new NumberFormatException("'" + buffer + "' has wrong format"); 602 } 603 } 604 return -result; 605 606 } 607 608 /** 609 * If timezone present - normalize dateTime [E Adding durations to 610 * dateTimes] 611 * 612 * @param date CCYY-MM-DDThh:mm:ss+03 613 */ 614 protected void normalize(DateTimeData date) { 615 616 // REVISIT: we have common code in addDuration() for durations 617 // should consider reorganizing it. 618 // 619 620 //add minutes (from time zone) 621 int negate = -1; 622 623 if (DEBUG) { 624 System.out.println("==>date.minute" + date.minute); 625 System.out.println("==>date.timezoneMin" + date.timezoneMin); 626 } 627 int temp = date.minute + negate * date.timezoneMin; 628 int carry = fQuotient(temp, 60); 629 date.minute = mod(temp, 60, carry); 630 631 if (DEBUG) { 632 System.out.println("==>carry: " + carry); 633 } 634 //add hours 635 temp = date.hour + negate * date.timezoneHr + carry; 636 carry = fQuotient(temp, 24); 637 date.hour = mod(temp, 24, carry); 638 if (DEBUG) { 639 System.out.println("==>date.hour" + date.hour); 640 System.out.println("==>carry: " + carry); 641 } 642 643 date.day = date.day + carry; 644 645 while (true) { 646 temp = maxDayInMonthFor(date.year, date.month); 647 if (date.day < 1) { 648 date.day = date.day + maxDayInMonthFor(date.year, date.month - 1); 649 carry = -1; 650 } else if (date.day > temp) { 651 date.day = date.day - temp; 652 carry = 1; 653 } else { 654 break; 655 } 656 temp = date.month + carry; 657 date.month = modulo(temp, 1, 13); 658 date.year = date.year + fQuotient(temp, 1, 13); 659 if (date.year == 0 && !Constants.SCHEMA_1_1_SUPPORT) { 660 date.year = (date.timezoneHr < 0 || date.timezoneMin < 0) ? 1 : -1; 661 } 662 } 663 date.utc = 'Z'; 664 } 665 666 /** 667 * @param date 668 */ 669 protected void saveUnnormalized(DateTimeData date) { 670 date.unNormYear = date.year; 671 date.unNormMonth = date.month; 672 date.unNormDay = date.day; 673 date.unNormHour = date.hour; 674 date.unNormMinute = date.minute; 675 date.unNormSecond = date.second; 676 } 677 678 /** 679 * Resets object representation of date/time 680 * 681 * @param data date/time object 682 */ 683 protected void resetDateObj(DateTimeData data) { 684 data.year = 0; 685 data.month = 0; 686 data.day = 0; 687 data.hour = 0; 688 data.minute = 0; 689 data.second = 0; 690 data.utc = 0; 691 data.timezoneHr = 0; 692 data.timezoneMin = 0; 693 } 694 695 /** 696 * Given {year,month} computes maximum number of days for given month 697 * 698 * @param year 699 * @param month 700 * @return integer containg the number of days in a given month 701 */ 702 protected int maxDayInMonthFor(int year, int month) { 703 //validate days 704 if (month == 4 || month == 6 || month == 9 || month == 11) { 705 return 30; 706 } else if (month == 2) { 707 if (isLeapYear(year)) { 708 return 29; 709 } else { 710 return 28; 711 } 712 } else { 713 return 31; 714 } 715 } 716 717 private boolean isLeapYear(int year) { 718 719 //REVISIT: should we take care about Julian calendar? 720 return ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0))); 721 } 722 723 // 724 // help function described in W3C PR Schema [E Adding durations to dateTimes] 725 // 726 protected int mod(int a, int b, int quotient) { 727 //modulo(a, b) = a - fQuotient(a,b)*b 728 return (a - quotient * b); 729 } 730 731 // 732 // help function described in W3C PR Schema [E Adding durations to dateTimes] 733 // 734 protected int fQuotient(int a, int b) { 735 736 //fQuotient(a, b) = the greatest integer less than or equal to a/b 737 return (int) Math.floor((float) a / b); 738 } 739 740 // 741 // help function described in W3C PR Schema [E Adding durations to dateTimes] 742 // 743 protected int modulo(int temp, int low, int high) { 744 //modulo(a - low, high - low) + low 745 int a = temp - low; 746 int b = high - low; 747 return (mod(a, b, fQuotient(a, b)) + low); 748 } 749 750 // 751 // help function described in W3C PR Schema [E Adding durations to dateTimes] 752 // 753 protected int fQuotient(int temp, int low, int high) { 754 //fQuotient(a - low, high - low) 755 756 return fQuotient(temp - low, high - low); 757 } 758 759 protected String dateToString(DateTimeData date) { 760 StringBuffer message = new StringBuffer(25); 761 append(message, date.year, 4); 762 message.append('-'); 763 append(message, date.month, 2); 764 message.append('-'); 765 append(message, date.day, 2); 766 message.append('T'); 767 append(message, date.hour, 2); 768 message.append(':'); 769 append(message, date.minute, 2); 770 message.append(':'); 771 append(message, date.second); 772 append(message, (char) date.utc, 0); 773 return message.toString(); 774 } 775 776 protected final void append(StringBuffer message, int value, int nch) { 777 if (value == Integer.MIN_VALUE) { 778 message.append(value); 779 return; 780 } 781 if (value < 0) { 782 message.append('-'); 783 value = -value; 784 } 785 if (nch == 4) { 786 if (value < 10) { 787 message.append("000"); 788 } else if (value < 100) { 789 message.append("00"); 790 } else if (value < 1000) { 791 message.append('0'); 792 } 793 message.append(value); 794 } else if (nch == 2) { 795 if (value < 10) { 796 message.append('0'); 797 } 798 message.append(value); 799 } else { 800 if (value != 0) { 801 message.append((char) value); 802 } 803 } 804 } 805 806 protected final void append(StringBuffer message, double value) { 807 if (value < 0) { 808 message.append('-'); 809 value = -value; 810 } 811 if (value < 10) { 812 message.append('0'); 813 } 814 append2(message, value); 815 } 816 817 protected final void append2(StringBuffer message, double value) { 818 final int intValue = (int) value; 819 if (value == intValue) { 820 message.append(intValue); 821 } else { 822 append3(message, value); 823 } 824 } 825 826 private void append3(StringBuffer message, double value) { 827 String d = String.valueOf(value); 828 int eIndex = d.indexOf('E'); 829 if (eIndex == -1) { 830 message.append(d); 831 return; 832 } 833 int exp; 834 if (value < 1) { 835 // Need to convert from scientific notation of the form 836 // n.nnn...E-N (N >= 4) to a normal decimal value. 837 try { 838 exp = parseInt(d, eIndex + 2, d.length()); 839 } // This should never happen. 840 // It's only possible if String.valueOf(double) is broken. 841 catch (Exception e) { 842 message.append(d); 843 return; 844 } 845 message.append("0."); 846 for (int i = 1; i < exp; ++i) { 847 message.append('0'); 848 } 849 // Remove trailing zeros. 850 int end = eIndex - 1; 851 while (end > 0) { 852 char c = d.charAt(end); 853 if (c != '0') { 854 break; 855 } 856 --end; 857 } 858 // Now append the digits to the end. Skip over the decimal point. 859 for (int i = 0; i <= end; ++i) { 860 char c = d.charAt(i); 861 if (c != '.') { 862 message.append(c); 863 } 864 } 865 } else { 866 // Need to convert from scientific notation of the form 867 // n.nnn...EN (N >= 7) to a normal decimal value. 868 try { 869 exp = parseInt(d, eIndex + 1, d.length()); 870 } // This should never happen. 871 // It's only possible if String.valueOf(double) is broken. 872 catch (Exception e) { 873 message.append(d); 874 return; 875 } 876 final int integerEnd = exp + 2; 877 for (int i = 0; i < eIndex; ++i) { 878 char c = d.charAt(i); 879 if (c != '.') { 880 if (i == integerEnd) { 881 message.append('.'); 882 } 883 message.append(c); 884 } 885 } 886 // Append trailing zeroes if necessary. 887 for (int i = integerEnd - eIndex; i > 0; --i) { 888 message.append('0'); 889 } 890 } 891 } 892 893 protected double parseSecond(String buffer, int start, int end) 894 throws NumberFormatException { 895 int dot = -1; 896 for (int i = start; i < end; i++) { 897 char ch = buffer.charAt(i); 898 if (ch == '.') { 899 dot = i; 900 } else if (ch > '9' || ch < '0') { 901 throw new NumberFormatException("'" + buffer + "' has wrong format"); 902 } 903 } 904 if (dot == -1) { 905 if (start + 2 != end) { 906 throw new NumberFormatException("'" + buffer + "' has wrong format"); 907 } 908 } else if (start + 2 != dot || dot + 1 == end) { 909 throw new NumberFormatException("'" + buffer + "' has wrong format"); 910 } 911 return Double.parseDouble(buffer.substring(start, end)); 912 } 913 914 // 915 //Private help functions 916 // 917 private void cloneDate(DateTimeData finalValue, DateTimeData tempDate) { 918 tempDate.year = finalValue.year; 919 tempDate.month = finalValue.month; 920 tempDate.day = finalValue.day; 921 tempDate.hour = finalValue.hour; 922 tempDate.minute = finalValue.minute; 923 tempDate.second = finalValue.second; 924 tempDate.utc = finalValue.utc; 925 tempDate.timezoneHr = finalValue.timezoneHr; 926 tempDate.timezoneMin = finalValue.timezoneMin; 927 } 928 929 /** 930 * Represents date time data 931 */ 932 static final class DateTimeData implements XSDateTime { 933 934 int year, month, day, hour, minute, utc; 935 double second; 936 int timezoneHr, timezoneMin; 937 private String originalValue; 938 boolean normalized = true; 939 int unNormYear; 940 int unNormMonth; 941 int unNormDay; 942 int unNormHour; 943 int unNormMinute; 944 double unNormSecond; 945 // used for comparisons - to decide the 'interesting' portions of 946 // a date/time based data type. 947 int position; 948 // a pointer to the type that was used go generate this data 949 // note that this is not the actual simple type, but one of the 950 // statically created XXXDV objects, so this won't cause any GC problem. 951 final AbstractDateTimeDV type; 952 private volatile String canonical; 953 954 public DateTimeData(String originalValue, AbstractDateTimeDV type) { 955 this.originalValue = originalValue; 956 this.type = type; 957 } 958 959 public DateTimeData(int year, int month, int day, int hour, int minute, 960 double second, int utc, String originalValue, boolean normalized, AbstractDateTimeDV type) { 961 this.year = year; 962 this.month = month; 963 this.day = day; 964 this.hour = hour; 965 this.minute = minute; 966 this.second = second; 967 this.utc = utc; 968 this.type = type; 969 this.originalValue = originalValue; 970 } 971 972 @Override 973 public boolean equals(Object obj) { 974 if (!(obj instanceof DateTimeData)) { 975 return false; 976 } 977 return type.compareDates(this, (DateTimeData) obj, true) == 0; 978 } 979 980 // If two DateTimeData are equals - then they should have the same 981 // hashcode. This means we need to convert the date to UTC before 982 // we return its hashcode. 983 // The DateTimeData is unfortunately mutable - so we cannot 984 // cache the result of the conversion... 985 // 986 @Override 987 public int hashCode() { 988 final DateTimeData tempDate = new DateTimeData(null, type); 989 type.cloneDate(this, tempDate); 990 type.normalize(tempDate); 991 return type.dateToString(tempDate).hashCode(); 992 } 993 994 @Override 995 public String toString() { 996 if (canonical == null) { 997 canonical = type.dateToString(this); 998 } 999 return canonical; 1000 } 1001 /* (non-Javadoc) 1002 * @see org.apache.xerces.xs.datatypes.XSDateTime#getYear() 1003 */ 1004 1005 @Override 1006 public int getYears() { 1007 if (type instanceof DurationDV) { 1008 return 0; 1009 } 1010 return normalized ? year : unNormYear; 1011 } 1012 /* (non-Javadoc) 1013 * @see org.apache.xerces.xs.datatypes.XSDateTime#getMonth() 1014 */ 1015 1016 @Override 1017 public int getMonths() { 1018 if (type instanceof DurationDV) { 1019 return year * 12 + month; 1020 } 1021 return normalized ? month : unNormMonth; 1022 } 1023 /* (non-Javadoc) 1024 * @see org.apache.xerces.xs.datatypes.XSDateTime#getDay() 1025 */ 1026 1027 @Override 1028 public int getDays() { 1029 if (type instanceof DurationDV) { 1030 return 0; 1031 } 1032 return normalized ? day : unNormDay; 1033 } 1034 /* (non-Javadoc) 1035 * @see org.apache.xerces.xs.datatypes.XSDateTime#getHour() 1036 */ 1037 1038 @Override 1039 public int getHours() { 1040 if (type instanceof DurationDV) { 1041 return 0; 1042 } 1043 return normalized ? hour : unNormHour; 1044 } 1045 /* (non-Javadoc) 1046 * @see org.apache.xerces.xs.datatypes.XSDateTime#getMinutes() 1047 */ 1048 1049 @Override 1050 public int getMinutes() { 1051 if (type instanceof DurationDV) { 1052 return 0; 1053 } 1054 return normalized ? minute : unNormMinute; 1055 } 1056 /* (non-Javadoc) 1057 * @see org.apache.xerces.xs.datatypes.XSDateTime#getSeconds() 1058 */ 1059 1060 @Override 1061 public double getSeconds() { 1062 if (type instanceof DurationDV) { 1063 return day * 24 * 60 * 60 + hour * 60 * 60 + minute * 60 + second; 1064 } 1065 return normalized ? second : unNormSecond; 1066 } 1067 /* (non-Javadoc) 1068 * @see org.apache.xerces.xs.datatypes.XSDateTime#hasTimeZone() 1069 */ 1070 1071 @Override 1072 public boolean hasTimeZone() { 1073 return utc != 0; 1074 } 1075 /* (non-Javadoc) 1076 * @see org.apache.xerces.xs.datatypes.XSDateTime#getTimeZoneHours() 1077 */ 1078 1079 @Override 1080 public int getTimeZoneHours() { 1081 return timezoneHr; 1082 } 1083 /* (non-Javadoc) 1084 * @see org.apache.xerces.xs.datatypes.XSDateTime#getTimeZoneMinutes() 1085 */ 1086 1087 @Override 1088 public int getTimeZoneMinutes() { 1089 return timezoneMin; 1090 } 1091 /* (non-Javadoc) 1092 * @see org.apache.xerces.xs.datatypes.XSDateTime#getLexicalValue() 1093 */ 1094 1095 @Override 1096 public String getLexicalValue() { 1097 return originalValue; 1098 } 1099 /* (non-Javadoc) 1100 * @see org.apache.xerces.xs.datatypes.XSDateTime#normalize() 1101 */ 1102 1103 @Override 1104 public XSDateTime normalize() { 1105 if (!normalized) { 1106 DateTimeData dt = (DateTimeData) this.clone(); 1107 dt.normalized = true; 1108 return dt; 1109 } 1110 return this; 1111 } 1112 /* (non-Javadoc) 1113 * @see org.apache.xerces.xs.datatypes.XSDateTime#isNormalized() 1114 */ 1115 1116 @Override 1117 public boolean isNormalized() { 1118 return normalized; 1119 } 1120 1121 @Override 1122 public Object clone() { 1123 DateTimeData dt = new DateTimeData(this.year, this.month, this.day, this.hour, 1124 this.minute, this.second, this.utc, this.originalValue, this.normalized, this.type); 1125 dt.canonical = this.canonical; 1126 dt.position = position; 1127 dt.timezoneHr = this.timezoneHr; 1128 dt.timezoneMin = this.timezoneMin; 1129 dt.unNormYear = this.unNormYear; 1130 dt.unNormMonth = this.unNormMonth; 1131 dt.unNormDay = this.unNormDay; 1132 dt.unNormHour = this.unNormHour; 1133 dt.unNormMinute = this.unNormMinute; 1134 dt.unNormSecond = this.unNormSecond; 1135 return dt; 1136 } 1137 1138 /* (non-Javadoc) 1139 * @see org.apache.xerces.xs.datatypes.XSDateTime#getXMLGregorianCalendar() 1140 */ 1141 @Override 1142 public XMLGregorianCalendar getXMLGregorianCalendar() { 1143 return type.getXMLGregorianCalendar(this); 1144 } 1145 /* (non-Javadoc) 1146 * @see org.apache.xerces.xs.datatypes.XSDateTime#getDuration() 1147 */ 1148 1149 @Override 1150 public Duration getDuration() { 1151 return type.getDuration(this); 1152 } 1153 } 1154 1155 protected XMLGregorianCalendar getXMLGregorianCalendar(DateTimeData data) { 1156 return null; 1157 } 1158 1159 protected Duration getDuration(DateTimeData data) { 1160 return null; 1161 } 1162 1163 protected final BigDecimal getFractionalSecondsAsBigDecimal(DateTimeData data) { 1164 final StringBuffer buf = new StringBuffer(); 1165 append3(buf, data.unNormSecond); 1166 String value = buf.toString(); 1167 final int index = value.indexOf('.'); 1168 if (index == -1) { 1169 return null; 1170 } 1171 value = value.substring(index); 1172 final BigDecimal _val = new BigDecimal(value); 1173 if (_val.compareTo(BigDecimal.valueOf(0)) == 0) { 1174 return null; 1175 } 1176 return _val; 1177 } 1178 } 1179