1 /* 2 * reserved comment block 3 * DO NOT REMOVE OR ALTER! 4 */ 5 /* 6 * Copyright 2005 The Apache Software Foundation. 7 * 8 * Licensed under the Apache License, Version 2.0 (the "License"); 9 * you may not use this file except in compliance with the License. 10 * You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 21 package com.sun.org.apache.xerces.internal.jaxp.datatype; 22 23 import java.io.IOException; 24 import java.io.ObjectStreamException; 25 import java.io.Serializable; 26 import java.math.BigDecimal; 27 import java.math.BigInteger; 28 import java.util.Calendar; 29 import java.util.Date; 30 import java.util.GregorianCalendar; 31 import java.util.TimeZone; 32 33 import javax.xml.datatype.DatatypeConstants; 34 import javax.xml.datatype.Duration; 35 import javax.xml.datatype.XMLGregorianCalendar; 36 37 import com.sun.org.apache.xerces.internal.util.DatatypeMessageFormatter; 38 39 /** 40 * <p>Immutable representation of a time span as defined in 41 * the W3C XML Schema 1.0 specification.</p> 42 * 43 * <p>A Duration object represents a period of Gregorian time, 44 * which consists of six fields (years, months, days, hours, 45 * minutes, and seconds) plus a sign (+/-) field.</p> 46 * 47 * <p>The first five fields have non-negative (>=0) integers or null 48 * (which represents that the field is not set), 49 * and the seconds field has a non-negative decimal or null. 50 * A negative sign indicates a negative duration.</p> 51 * 52 * <p>This class provides a number of methods that make it easy 53 * to use for the duration datatype of XML Schema 1.0 with 54 * the errata.</p> 55 * 56 * <h2>Order relationship</h2> 57 * <p>Duration objects only have partial order, where two values A and B 58 * maybe either:</p> 59 * <ol> 60 * <li>A<B (A is shorter than B) 61 * <li>A>B (A is longer than B) 62 * <li>A==B (A and B are of the same duration) 63 * <li>A<>B (Comparison between A and B is indeterminate) 64 * </ol> 65 * <p>For example, 30 days cannot be meaningfully compared to one month. 66 * The {@link #compare(Duration)} method implements this 67 * relationship.</p> 68 * 69 * <p>See the {@link #isLongerThan(Duration)} method for details about 70 * the order relationship among {@link Duration} objects.</p> 71 * 72 * 73 * 74 * <h2>Operations over Duration</h2> 75 * <p>This class provides a set of basic arithmetic operations, such 76 * as addition, subtraction and multiplication. 77 * Because durations don't have total order, an operation could 78 * fail for some combinations of operations. For example, you cannot 79 * subtract 15 days from 1 month. See the javadoc of those methods 80 * for detailed conditions where this could happen.</p> 81 * 82 * <p>Also, division of a duration by a number is not provided because 83 * the {@link Duration} class can only deal with finite precision 84 * decimal numbers. For example, one cannot represent 1 sec divided by 3.</p> 85 * 86 * <p>However, you could substitute a division by 3 with multiplying 87 * by numbers such as 0.3 or 0.333.</p> 88 * 89 * 90 * 91 * <h2>Range of allowed values</h2> 92 * <p> 93 * Because some operations of {@link Duration} rely on {@link Calendar} 94 * even though {@link Duration} can hold very large or very small values, 95 * some of the methods may not work correctly on such {@link Duration}s. 96 * The impacted methods document their dependency on {@link Calendar}. 97 * 98 * 99 * @author <a href="mailto:Kohsuke.Kawaguchi@Sun.com">Kohsuke Kawaguchi</a> 100 * @author <a href="mailto:Joseph.Fialli@Sun.com">Joseph Fialli</a> 101 * @version $Revision: 1.8 $, $Date: 2010/05/19 23:20:06 $ 102 103 * @see XMLGregorianCalendar#add(Duration) 104 */ 105 class DurationImpl 106 extends Duration 107 implements Serializable { 108 109 /** 110 * <p>Number of Fields.</p> 111 */ 112 private static final int FIELD_NUM = 6; 113 114 /** 115 * <p>Internal array of value Fields.</p> 116 */ 117 private static final DatatypeConstants.Field[] FIELDS = new DatatypeConstants.Field[]{ 118 DatatypeConstants.YEARS, 119 DatatypeConstants.MONTHS, 120 DatatypeConstants.DAYS, 121 DatatypeConstants.HOURS, 122 DatatypeConstants.MINUTES, 123 DatatypeConstants.SECONDS 124 }; 125 126 /** 127 * <p>Internal array of value Field ids.</p> 128 */ 129 private static final int[] FIELD_IDS = { 130 DatatypeConstants.YEARS.getId(), 131 DatatypeConstants.MONTHS.getId(), 132 DatatypeConstants.DAYS.getId(), 133 DatatypeConstants.HOURS.getId(), 134 DatatypeConstants.MINUTES.getId(), 135 DatatypeConstants.SECONDS.getId() 136 }; 137 138 /** 139 * TimeZone for GMT. 140 */ 141 private static final TimeZone GMT = TimeZone.getTimeZone("GMT"); 142 143 /** 144 * <p>BigDecimal value of 0.</p> 145 */ 146 private static final BigDecimal ZERO = BigDecimal.valueOf((long) 0); 147 148 /** 149 * <p>Indicates the sign. -1, 0 or 1 if the duration is negative, 150 * zero, or positive.</p> 151 */ 152 protected int signum; 153 154 /** 155 * <p>Years of this <code>Duration</code>.</p> 156 */ 157 /** 158 * These were final since Duration is immutable. But new subclasses need 159 * to be able to set after conversion. It won't break the immutable nature 160 * of them since there's no other way to set new values to them 161 */ 162 protected BigInteger years; 163 164 /** 165 * <p>Months of this <code>Duration</code>.</p> 166 */ 167 protected BigInteger months; 168 169 /** 170 * <p>Days of this <code>Duration</code>.</p> 171 */ 172 protected BigInteger days; 173 174 /** 175 * <p>Hours of this <code>Duration</code>.</p> 176 */ 177 protected BigInteger hours; 178 179 /** 180 * <p>Minutes of this <code>Duration</code>.</p> 181 */ 182 protected BigInteger minutes; 183 184 /** 185 * <p>Seconds of this <code>Duration</code>.</p> 186 */ 187 protected BigDecimal seconds; 188 189 /** 190 * Returns the sign of this duration in -1,0, or 1. 191 * 192 * @return 193 * -1 if this duration is negative, 0 if the duration is zero, 194 * and 1 if the duration is postive. 195 */ 196 public int getSign() { 197 198 return signum; 199 } 200 201 /** 202 * TODO: Javadoc 203 * @param isPositive Sign. 204 * 205 * @return 1 if positive, else -1. 206 */ 207 protected int calcSignum(boolean isPositive) { 208 if ((years == null || years.signum() == 0) 209 && (months == null || months.signum() == 0) 210 && (days == null || days.signum() == 0) 211 && (hours == null || hours.signum() == 0) 212 && (minutes == null || minutes.signum() == 0) 213 && (seconds == null || seconds.signum() == 0)) { 214 return 0; 215 } 216 217 if (isPositive) { 218 return 1; 219 } else { 220 return -1; 221 } 222 223 } 224 225 /** 226 * <p>Constructs a new Duration object by specifying each field individually.</p> 227 * 228 * <p>All the parameters are optional as long as at least one field is present. 229 * If specified, parameters have to be zero or positive.</p> 230 * 231 * @param isPositive Set to <code>false</code> to create a negative duration. When the length 232 * of the duration is zero, this parameter will be ignored. 233 * @param years of this <code>Duration</code> 234 * @param months of this <code>Duration</code> 235 * @param days of this <code>Duration</code> 236 * @param hours of this <code>Duration</code> 237 * @param minutes of this <code>Duration</code> 238 * @param seconds of this <code>Duration</code> 239 * 240 * @throws IllegalArgumentException 241 * If years, months, days, hours, minutes and 242 * seconds parameters are all <code>null</code>. Or if any 243 * of those parameters are negative. 244 */ 245 protected DurationImpl( 246 boolean isPositive, 247 BigInteger years, 248 BigInteger months, 249 BigInteger days, 250 BigInteger hours, 251 BigInteger minutes, 252 BigDecimal seconds) { 253 254 this.years = years; 255 this.months = months; 256 this.days = days; 257 this.hours = hours; 258 this.minutes = minutes; 259 this.seconds = seconds; 260 261 this.signum = calcSignum(isPositive); 262 263 // sanity check 264 if (years == null 265 && months == null 266 && days == null 267 && hours == null 268 && minutes == null 269 && seconds == null) { 270 throw new IllegalArgumentException( 271 //"all the fields are null" 272 DatatypeMessageFormatter.formatMessage(null, "AllFieldsNull", null) 273 ); 274 } 275 testNonNegative(years, DatatypeConstants.YEARS); 276 testNonNegative(months, DatatypeConstants.MONTHS); 277 testNonNegative(days, DatatypeConstants.DAYS); 278 testNonNegative(hours, DatatypeConstants.HOURS); 279 testNonNegative(minutes, DatatypeConstants.MINUTES); 280 testNonNegative(seconds, DatatypeConstants.SECONDS); 281 } 282 283 /** 284 * <p>Makes sure that the given number is non-negative. If it is not, 285 * throw {@link IllegalArgumentException}.</p> 286 * 287 * @param n Number to test. 288 * @param f Field to test. 289 */ 290 protected static void testNonNegative(BigInteger n, DatatypeConstants.Field f) { 291 if (n != null && n.signum() < 0) { 292 throw new IllegalArgumentException( 293 DatatypeMessageFormatter.formatMessage(null, "NegativeField", new Object[]{f.toString()}) 294 ); 295 } 296 } 297 298 /** 299 * <p>Makes sure that the given number is non-negative. If it is not, 300 * throw {@link IllegalArgumentException}.</p> 301 * 302 * @param n Number to test. 303 * @param f Field to test. 304 */ 305 protected static void testNonNegative(BigDecimal n, DatatypeConstants.Field f) { 306 if (n != null && n.signum() < 0) { 307 308 throw new IllegalArgumentException( 309 DatatypeMessageFormatter.formatMessage(null, "NegativeField", new Object[]{f.toString()}) 310 ); 311 } 312 } 313 314 /** 315 * <p>Constructs a new Duration object by specifying each field 316 * individually.</p> 317 * 318 * <p>This method is functionally equivalent to 319 * invoking another constructor by wrapping 320 * all non-zero parameters into {@link BigInteger} and {@link BigDecimal}. 321 * Zero value of int parameter is equivalent of null value of 322 * the corresponding field.</p> 323 * 324 * @see #DurationImpl(boolean, BigInteger, BigInteger, BigInteger, BigInteger, 325 * BigInteger, BigDecimal) 326 */ 327 protected DurationImpl( 328 final boolean isPositive, 329 final int years, 330 final int months, 331 final int days, 332 final int hours, 333 final int minutes, 334 final int seconds) { 335 this( 336 isPositive, 337 wrap(years), 338 wrap(months), 339 wrap(days), 340 wrap(hours), 341 wrap(minutes), 342 seconds != DatatypeConstants.FIELD_UNDEFINED ? new BigDecimal(String.valueOf(seconds)) : null); 343 } 344 345 /** 346 * TODO: Javadoc 347 * 348 * @param i int to convert to BigInteger. 349 * 350 * @return BigInteger representation of int. 351 */ 352 protected static BigInteger wrap(final int i) { 353 354 // field may not be set 355 if (i == DatatypeConstants.FIELD_UNDEFINED) { 356 return null; 357 } 358 359 // int -> BigInteger 360 return new BigInteger(String.valueOf(i)); 361 } 362 363 /** 364 * <p>Constructs a new Duration object by specifying the duration 365 * in milliseconds.</p> 366 * 367 * @param durationInMilliSeconds 368 * The length of the duration in milliseconds. 369 */ 370 protected DurationImpl(final long durationInMilliSeconds) { 371 372 long l = durationInMilliSeconds; 373 374 if (l > 0) { 375 signum = 1; 376 } else if (l < 0) { 377 signum = -1; 378 if (l == 0x8000000000000000L) { 379 // negating 0x8000000000000000L causes an overflow 380 l++; 381 } 382 l *= -1; 383 } else { 384 signum = 0; 385 } 386 387 // let GregorianCalendar do the heavy lifting 388 GregorianCalendar gregorianCalendar = new GregorianCalendar(GMT); 389 390 // duration is the offset from the Epoch 391 gregorianCalendar.setTimeInMillis(l); 392 393 // now find out how much each field has changed 394 long int2long = 0L; 395 396 // years 397 int2long = gregorianCalendar.get(Calendar.YEAR) - 1970; 398 this.years = BigInteger.valueOf(int2long); 399 400 // months 401 int2long = gregorianCalendar.get(Calendar.MONTH); 402 this.months = BigInteger.valueOf(int2long); 403 404 // days 405 int2long = gregorianCalendar.get(Calendar.DAY_OF_MONTH) - 1; 406 this.days = BigInteger.valueOf(int2long); 407 408 // hours 409 int2long = gregorianCalendar.get(Calendar.HOUR_OF_DAY); 410 this.hours = BigInteger.valueOf(int2long); 411 412 // minutes 413 int2long = gregorianCalendar.get(Calendar.MINUTE); 414 this.minutes = BigInteger.valueOf(int2long); 415 416 // seconds & milliseconds 417 int2long = (gregorianCalendar.get(Calendar.SECOND) * 1000) 418 + gregorianCalendar.get(Calendar.MILLISECOND); 419 this.seconds = BigDecimal.valueOf(int2long, 3); 420 } 421 422 /** 423 * Constructs a new Duration object by 424 * parsing its string representation 425 * "PnYnMnDTnHnMnS" as defined in XML Schema 1.0 section 3.2.6.1. 426 * 427 * <p> 428 * The string representation may not have any leading 429 * and trailing whitespaces. 430 * 431 * <p> 432 * For example, this method parses strings like 433 * "P1D" (1 day), "-PT100S" (-100 sec.), "P1DT12H" (1 days and 12 hours). 434 * 435 * <p> 436 * The parsing is done field by field so that 437 * the following holds for any lexically correct string x: 438 * <pre> 439 * new Duration(x).toString().equals(x) 440 * </pre> 441 * 442 * Returns a non-null valid duration object that holds the value 443 * indicated by the lexicalRepresentation parameter. 444 * 445 * @param lexicalRepresentation 446 * Lexical representation of a duration. 447 * @throws IllegalArgumentException 448 * If the given string does not conform to the aforementioned 449 * specification. 450 * @throws NullPointerException 451 * If the given string is null. 452 */ 453 protected DurationImpl(String lexicalRepresentation) 454 throws IllegalArgumentException { 455 // only if I could use the JDK1.4 regular expression .... 456 457 final String s = lexicalRepresentation; 458 boolean positive; 459 int[] idx = new int[1]; 460 int length = s.length(); 461 boolean timeRequired = false; 462 463 if (lexicalRepresentation == null) { 464 throw new NullPointerException(); 465 } 466 467 idx[0] = 0; 468 if (length != idx[0] && s.charAt(idx[0]) == '-') { 469 idx[0]++; 470 positive = false; 471 } else { 472 positive = true; 473 } 474 475 if (length != idx[0] && s.charAt(idx[0]++) != 'P') { 476 throw new IllegalArgumentException(s); //,idx[0]-1); 477 } 478 479 480 // phase 1: chop the string into chunks 481 // (where a chunk is '<number><a symbol>' 482 //-------------------------------------- 483 int dateLen = 0; 484 String[] dateParts = new String[3]; 485 int[] datePartsIndex = new int[3]; 486 while (length != idx[0] 487 && isDigit(s.charAt(idx[0])) 488 && dateLen < 3) { 489 datePartsIndex[dateLen] = idx[0]; 490 dateParts[dateLen++] = parsePiece(s, idx); 491 } 492 493 if (length != idx[0]) { 494 if (s.charAt(idx[0]++) == 'T') { 495 timeRequired = true; 496 } else { 497 throw new IllegalArgumentException(s); // ,idx[0]-1); 498 } 499 } 500 501 int timeLen = 0; 502 String[] timeParts = new String[3]; 503 int[] timePartsIndex = new int[3]; 504 while (length != idx[0] 505 && isDigitOrPeriod(s.charAt(idx[0])) 506 && timeLen < 3) { 507 timePartsIndex[timeLen] = idx[0]; 508 timeParts[timeLen++] = parsePiece(s, idx); 509 } 510 511 if (timeRequired && timeLen == 0) { 512 throw new IllegalArgumentException(s); // ,idx[0]); 513 } 514 515 if (length != idx[0]) { 516 throw new IllegalArgumentException(s); // ,idx[0]); 517 } 518 if (dateLen == 0 && timeLen == 0) { 519 throw new IllegalArgumentException(s); // ,idx[0]); 520 } 521 522 // phase 2: check the ordering of chunks 523 //-------------------------------------- 524 organizeParts(s, dateParts, datePartsIndex, dateLen, "YMD"); 525 organizeParts(s, timeParts, timePartsIndex, timeLen, "HMS"); 526 527 // parse into numbers 528 years = parseBigInteger(s, dateParts[0], datePartsIndex[0]); 529 months = parseBigInteger(s, dateParts[1], datePartsIndex[1]); 530 days = parseBigInteger(s, dateParts[2], datePartsIndex[2]); 531 hours = parseBigInteger(s, timeParts[0], timePartsIndex[0]); 532 minutes = parseBigInteger(s, timeParts[1], timePartsIndex[1]); 533 seconds = parseBigDecimal(s, timeParts[2], timePartsIndex[2]); 534 signum = calcSignum(positive); 535 } 536 537 538 /** 539 * TODO: Javadoc 540 * 541 * @param ch char to test. 542 * 543 * @return true if ch is a digit, else false. 544 */ 545 private static boolean isDigit(char ch) { 546 return '0' <= ch && ch <= '9'; 547 } 548 549 /** 550 * TODO: Javadoc 551 * 552 * @param ch to test. 553 * 554 * @return true if ch is a digit or a period, else false. 555 */ 556 private static boolean isDigitOrPeriod(char ch) { 557 return isDigit(ch) || ch == '.'; 558 } 559 560 /** 561 * TODO: Javadoc 562 * 563 * @param whole String to parse. 564 * @param idx TODO: ??? 565 * 566 * @return Result of parsing. 567 * 568 * @throws IllegalArgumentException If whole cannot be parsed. 569 */ 570 private static String parsePiece(String whole, int[] idx) 571 throws IllegalArgumentException { 572 int start = idx[0]; 573 while (idx[0] < whole.length() 574 && isDigitOrPeriod(whole.charAt(idx[0]))) { 575 idx[0]++; 576 } 577 if (idx[0] == whole.length()) { 578 throw new IllegalArgumentException(whole); // ,idx[0]); 579 } 580 581 idx[0]++; 582 583 return whole.substring(start, idx[0]); 584 } 585 586 /** 587 * TODO: Javadoc. 588 * 589 * @param whole TODO: ??? 590 * @param parts TODO: ??? 591 * @param partsIndex TODO: ??? 592 * @param len TODO: ??? 593 * @param tokens TODO: ??? 594 * 595 * @throws IllegalArgumentException TODO: ??? 596 */ 597 private static void organizeParts( 598 String whole, 599 String[] parts, 600 int[] partsIndex, 601 int len, 602 String tokens) 603 throws IllegalArgumentException { 604 605 int idx = tokens.length(); 606 for (int i = len - 1; i >= 0; i--) { 607 int nidx = 608 tokens.lastIndexOf( 609 parts[i].charAt(parts[i].length() - 1), 610 idx - 1); 611 if (nidx == -1) { 612 throw new IllegalArgumentException(whole); 613 // ,partsIndex[i]+parts[i].length()-1); 614 } 615 616 for (int j = nidx + 1; j < idx; j++) { 617 parts[j] = null; 618 } 619 idx = nidx; 620 parts[idx] = parts[i]; 621 partsIndex[idx] = partsIndex[i]; 622 } 623 for (idx--; idx >= 0; idx--) { 624 parts[idx] = null; 625 } 626 } 627 628 /** 629 * TODO: Javadoc 630 * 631 * @param whole TODO: ???. 632 * @param part TODO: ???. 633 * @param index TODO: ???. 634 * 635 * @return TODO: ???. 636 * 637 * @throws IllegalArgumentException TODO: ???. 638 */ 639 private static BigInteger parseBigInteger( 640 String whole, 641 String part, 642 int index) 643 throws IllegalArgumentException { 644 if (part == null) { 645 return null; 646 } 647 part = part.substring(0, part.length() - 1); 648 // try { 649 return new BigInteger(part); 650 // } catch( NumberFormatException e ) { 651 // throw new ParseException( whole, index ); 652 // } 653 } 654 655 /** 656 * TODO: Javadoc. 657 * 658 * @param whole TODO: ???. 659 * @param part TODO: ???. 660 * @param index TODO: ???. 661 * 662 * @return TODO: ???. 663 * 664 * @throws IllegalArgumentException TODO: ???. 665 */ 666 private static BigDecimal parseBigDecimal( 667 String whole, 668 String part, 669 int index) 670 throws IllegalArgumentException { 671 if (part == null) { 672 return null; 673 } 674 part = part.substring(0, part.length() - 1); 675 // NumberFormatException is IllegalArgumentException 676 // try { 677 return new BigDecimal(part); 678 // } catch( NumberFormatException e ) { 679 // throw new ParseException( whole, index ); 680 // } 681 } 682 683 /** 684 * <p>Four constants defined for the comparison of durations.</p> 685 */ 686 private static final XMLGregorianCalendar[] TEST_POINTS = new XMLGregorianCalendar[] { 687 XMLGregorianCalendarImpl.parse("1696-09-01T00:00:00Z"), 688 XMLGregorianCalendarImpl.parse("1697-02-01T00:00:00Z"), 689 XMLGregorianCalendarImpl.parse("1903-03-01T00:00:00Z"), 690 XMLGregorianCalendarImpl.parse("1903-07-01T00:00:00Z") 691 }; 692 693 /** 694 * <p>Partial order relation comparison with this <code>Duration</code> instance.</p> 695 * 696 * <p>Comparison result must be in accordance with 697 * <a href="http://www.w3.org/TR/xmlschema-2/#duration-order">W3C XML Schema 1.0 Part 2, Section 3.2.7.6.2, 698 * <i>Order relation on duration</i></a>.</p> 699 * 700 * <p>Return:</p> 701 * <ul> 702 * <li>{@link DatatypeConstants#LESSER} if this <code>Duration</code> is shorter than <code>duration</code> parameter</li> 703 * <li>{@link DatatypeConstants#EQUAL} if this <code>Duration</code> is equal to <code>duration</code> parameter</li> 704 * <li>{@link DatatypeConstants#GREATER} if this <code>Duration</code> is longer than <code>duration</code> parameter</li> 705 * <li>{@link DatatypeConstants#INDETERMINATE} if a conclusive partial order relation cannot be determined</li> 706 * </ul> 707 * 708 * @param duration to compare 709 * 710 * @return the relationship between <code>this</code> <code>Duration</code>and <code>duration</code> parameter as 711 * {@link DatatypeConstants#LESSER}, {@link DatatypeConstants#EQUAL}, {@link DatatypeConstants#GREATER} 712 * or {@link DatatypeConstants#INDETERMINATE}. 713 * 714 * @throws UnsupportedOperationException If the underlying implementation 715 * cannot reasonably process the request, e.g. W3C XML Schema allows for 716 * arbitrarily large/small/precise values, the request may be beyond the 717 * implementations capability. 718 * @throws NullPointerException if <code>duration</code> is <code>null</code>. 719 * 720 * @see #isShorterThan(Duration) 721 * @see #isLongerThan(Duration) 722 */ 723 public int compare(Duration rhs) { 724 725 BigInteger maxintAsBigInteger = BigInteger.valueOf((long) Integer.MAX_VALUE); 726 BigInteger minintAsBigInteger = BigInteger.valueOf((long) Integer.MIN_VALUE); 727 728 // check for fields that are too large in this Duration 729 if (years != null && years.compareTo(maxintAsBigInteger) == 1) { 730 throw new UnsupportedOperationException( 731 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 732 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.YEARS.toString(), years.toString()}) 733 //this.getClass().getName() + "#compare(Duration duration)" 734 //+ " years too large to be supported by this implementation " 735 //+ years.toString() 736 ); 737 } 738 if (months != null && months.compareTo(maxintAsBigInteger) == 1) { 739 throw new UnsupportedOperationException( 740 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 741 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.MONTHS.toString(), months.toString()}) 742 743 //this.getClass().getName() + "#compare(Duration duration)" 744 //+ " months too large to be supported by this implementation " 745 //+ months.toString() 746 ); 747 } 748 if (days != null && days.compareTo(maxintAsBigInteger) == 1) { 749 throw new UnsupportedOperationException( 750 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 751 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.DAYS.toString(), days.toString()}) 752 753 //this.getClass().getName() + "#compare(Duration duration)" 754 //+ " days too large to be supported by this implementation " 755 //+ days.toString() 756 ); 757 } 758 if (hours != null && hours.compareTo(maxintAsBigInteger) == 1) { 759 throw new UnsupportedOperationException( 760 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 761 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.HOURS.toString(), hours.toString()}) 762 763 //this.getClass().getName() + "#compare(Duration duration)" 764 //+ " hours too large to be supported by this implementation " 765 //+ hours.toString() 766 ); 767 } 768 if (minutes != null && minutes.compareTo(maxintAsBigInteger) == 1) { 769 throw new UnsupportedOperationException( 770 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 771 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.MINUTES.toString(), minutes.toString()}) 772 773 //this.getClass().getName() + "#compare(Duration duration)" 774 //+ " minutes too large to be supported by this implementation " 775 //+ minutes.toString() 776 ); 777 } 778 if (seconds != null && seconds.toBigInteger().compareTo(maxintAsBigInteger) == 1) { 779 throw new UnsupportedOperationException( 780 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 781 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.SECONDS.toString(), seconds.toString()}) 782 783 //this.getClass().getName() + "#compare(Duration duration)" 784 //+ " seconds too large to be supported by this implementation " 785 //+ seconds.toString() 786 ); 787 } 788 789 // check for fields that are too large in rhs Duration 790 BigInteger rhsYears = (BigInteger) rhs.getField(DatatypeConstants.YEARS); 791 if (rhsYears != null && rhsYears.compareTo(maxintAsBigInteger) == 1) { 792 throw new UnsupportedOperationException( 793 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 794 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.YEARS.toString(), rhsYears.toString()}) 795 796 //this.getClass().getName() + "#compare(Duration duration)" 797 //+ " years too large to be supported by this implementation " 798 //+ rhsYears.toString() 799 ); 800 } 801 BigInteger rhsMonths = (BigInteger) rhs.getField(DatatypeConstants.MONTHS); 802 if (rhsMonths != null && rhsMonths.compareTo(maxintAsBigInteger) == 1) { 803 throw new UnsupportedOperationException( 804 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 805 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.MONTHS.toString(), rhsMonths.toString()}) 806 807 //this.getClass().getName() + "#compare(Duration duration)" 808 //+ " months too large to be supported by this implementation " 809 //+ rhsMonths.toString() 810 ); 811 } 812 BigInteger rhsDays = (BigInteger) rhs.getField(DatatypeConstants.DAYS); 813 if (rhsDays != null && rhsDays.compareTo(maxintAsBigInteger) == 1) { 814 throw new UnsupportedOperationException( 815 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 816 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.DAYS.toString(), rhsDays.toString()}) 817 818 //this.getClass().getName() + "#compare(Duration duration)" 819 //+ " days too large to be supported by this implementation " 820 //+ rhsDays.toString() 821 ); 822 } 823 BigInteger rhsHours = (BigInteger) rhs.getField(DatatypeConstants.HOURS); 824 if (rhsHours != null && rhsHours.compareTo(maxintAsBigInteger) == 1) { 825 throw new UnsupportedOperationException( 826 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 827 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.HOURS.toString(), rhsHours.toString()}) 828 829 //this.getClass().getName() + "#compare(Duration duration)" 830 //+ " hours too large to be supported by this implementation " 831 //+ rhsHours.toString() 832 ); 833 } 834 BigInteger rhsMinutes = (BigInteger) rhs.getField(DatatypeConstants.MINUTES); 835 if (rhsMinutes != null && rhsMinutes.compareTo(maxintAsBigInteger) == 1) { 836 throw new UnsupportedOperationException( 837 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 838 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.MINUTES.toString(), rhsMinutes.toString()}) 839 840 //this.getClass().getName() + "#compare(Duration duration)" 841 //+ " minutes too large to be supported by this implementation " 842 //+ rhsMinutes.toString() 843 ); 844 } 845 BigDecimal rhsSecondsAsBigDecimal = (BigDecimal) rhs.getField(DatatypeConstants.SECONDS); 846 BigInteger rhsSeconds = null; 847 if ( rhsSecondsAsBigDecimal != null ) { 848 rhsSeconds = rhsSecondsAsBigDecimal.toBigInteger(); 849 } 850 if (rhsSeconds != null && rhsSeconds.compareTo(maxintAsBigInteger) == 1) { 851 throw new UnsupportedOperationException( 852 DatatypeMessageFormatter.formatMessage(null, "TooLarge", 853 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.SECONDS.toString(), rhsSeconds.toString()}) 854 855 //this.getClass().getName() + "#compare(Duration duration)" 856 //+ " seconds too large to be supported by this implementation " 857 //+ rhsSeconds.toString() 858 ); 859 } 860 861 // turn this Duration into a GregorianCalendar 862 GregorianCalendar lhsCalendar = new GregorianCalendar( 863 1970, 864 1, 865 1, 866 0, 867 0, 868 0); 869 lhsCalendar.add(GregorianCalendar.YEAR, getYears() * getSign()); 870 lhsCalendar.add(GregorianCalendar.MONTH, getMonths() * getSign()); 871 lhsCalendar.add(GregorianCalendar.DAY_OF_YEAR, getDays() * getSign()); 872 lhsCalendar.add(GregorianCalendar.HOUR_OF_DAY, getHours() * getSign()); 873 lhsCalendar.add(GregorianCalendar.MINUTE, getMinutes() * getSign()); 874 lhsCalendar.add(GregorianCalendar.SECOND, getSeconds() * getSign()); 875 876 // turn compare Duration into a GregorianCalendar 877 GregorianCalendar rhsCalendar = new GregorianCalendar( 878 1970, 879 1, 880 1, 881 0, 882 0, 883 0); 884 rhsCalendar.add(GregorianCalendar.YEAR, rhs.getYears() * rhs.getSign()); 885 rhsCalendar.add(GregorianCalendar.MONTH, rhs.getMonths() * rhs.getSign()); 886 rhsCalendar.add(GregorianCalendar.DAY_OF_YEAR, rhs.getDays() * rhs.getSign()); 887 rhsCalendar.add(GregorianCalendar.HOUR_OF_DAY, rhs.getHours() * rhs.getSign()); 888 rhsCalendar.add(GregorianCalendar.MINUTE, rhs.getMinutes() * rhs.getSign()); 889 rhsCalendar.add(GregorianCalendar.SECOND, rhs.getSeconds() * rhs.getSign()); 890 891 892 if (lhsCalendar.equals(rhsCalendar)) { 893 return DatatypeConstants.EQUAL; 894 } 895 896 return compareDates(this, rhs); 897 } 898 899 /** 900 * Compares 2 given durations. (refer to W3C Schema Datatypes "3.2.6 duration") 901 * 902 * @param duration1 Unnormalized duration 903 * @param duration2 Unnormalized duration 904 * @return INDETERMINATE if the order relationship between date1 and date2 is indeterminate. 905 * EQUAL if the order relation between date1 and date2 is EQUAL. 906 * If the strict parameter is true, return LESS_THAN if date1 is less than date2 and 907 * return GREATER_THAN if date1 is greater than date2. 908 * If the strict parameter is false, return LESS_THAN if date1 is less than OR equal to date2 and 909 * return GREATER_THAN if date1 is greater than OR equal to date2 910 */ 911 private int compareDates(Duration duration1, Duration duration2) { 912 913 int resultA = DatatypeConstants.INDETERMINATE; 914 int resultB = DatatypeConstants.INDETERMINATE; 915 916 XMLGregorianCalendar tempA = (XMLGregorianCalendar)TEST_POINTS[0].clone(); 917 XMLGregorianCalendar tempB = (XMLGregorianCalendar)TEST_POINTS[0].clone(); 918 919 //long comparison algorithm is required 920 tempA.add(duration1); 921 tempB.add(duration2); 922 resultA = tempA.compare(tempB); 923 if ( resultA == DatatypeConstants.INDETERMINATE ) { 924 return DatatypeConstants.INDETERMINATE; 925 } 926 927 tempA = (XMLGregorianCalendar)TEST_POINTS[1].clone(); 928 tempB = (XMLGregorianCalendar)TEST_POINTS[1].clone(); 929 930 tempA.add(duration1); 931 tempB.add(duration2); 932 resultB = tempA.compare(tempB); 933 resultA = compareResults(resultA, resultB); 934 if (resultA == DatatypeConstants.INDETERMINATE) { 935 return DatatypeConstants.INDETERMINATE; 936 } 937 938 tempA = (XMLGregorianCalendar)TEST_POINTS[2].clone(); 939 tempB = (XMLGregorianCalendar)TEST_POINTS[2].clone(); 940 941 tempA.add(duration1); 942 tempB.add(duration2); 943 resultB = tempA.compare(tempB); 944 resultA = compareResults(resultA, resultB); 945 if (resultA == DatatypeConstants.INDETERMINATE) { 946 return DatatypeConstants.INDETERMINATE; 947 } 948 949 tempA = (XMLGregorianCalendar)TEST_POINTS[3].clone(); 950 tempB = (XMLGregorianCalendar)TEST_POINTS[3].clone(); 951 952 tempA.add(duration1); 953 tempB.add(duration2); 954 resultB = tempA.compare(tempB); 955 resultA = compareResults(resultA, resultB); 956 957 return resultA; 958 } 959 960 private int compareResults(int resultA, int resultB){ 961 962 if ( resultB == DatatypeConstants.INDETERMINATE ) { 963 return DatatypeConstants.INDETERMINATE; 964 } 965 else if ( resultA!=resultB) { 966 return DatatypeConstants.INDETERMINATE; 967 } 968 return resultA; 969 } 970 971 /** 972 * Returns a hash code consistent with the definition of the equals method. 973 * 974 * @see Object#hashCode() 975 */ 976 public int hashCode() { 977 // component wise hash is not correct because 1day = 24hours 978 Calendar cal = TEST_POINTS[0].toGregorianCalendar(); 979 this.addTo(cal); 980 return (int) getCalendarTimeInMillis(cal); 981 } 982 983 /** 984 * Returns a string representation of this duration object. 985 * 986 * <p> 987 * The result is formatter according to the XML Schema 1.0 988 * spec and can be always parsed back later into the 989 * equivalent duration object by 990 * the {@link #DurationImpl(String)} constructor. 991 * 992 * <p> 993 * Formally, the following holds for any {@link Duration} 994 * object x. 995 * <pre> 996 * new Duration(x.toString()).equals(x) 997 * </pre> 998 * 999 * @return 1000 * Always return a non-null valid String object. 1001 */ 1002 public String toString() { 1003 StringBuffer buf = new StringBuffer(); 1004 if (signum < 0) { 1005 buf.append('-'); 1006 } 1007 buf.append('P'); 1008 1009 if (years != null) { 1010 buf.append(years + "Y"); 1011 } 1012 if (months != null) { 1013 buf.append(months + "M"); 1014 } 1015 if (days != null) { 1016 buf.append(days + "D"); 1017 } 1018 1019 if (hours != null || minutes != null || seconds != null) { 1020 buf.append('T'); 1021 if (hours != null) { 1022 buf.append(hours + "H"); 1023 } 1024 if (minutes != null) { 1025 buf.append(minutes + "M"); 1026 } 1027 if (seconds != null) { 1028 buf.append(toString(seconds) + "S"); 1029 } 1030 } 1031 1032 return buf.toString(); 1033 } 1034 1035 /** 1036 * <p>Turns {@link BigDecimal} to a string representation.</p> 1037 * 1038 * <p>Due to a behavior change in the {@link BigDecimal#toString()} 1039 * method in JDK1.5, this had to be implemented here.</p> 1040 * 1041 * @param bd <code>BigDecimal</code> to format as a <code>String</code> 1042 * 1043 * @return <code>String</code> representation of <code>BigDecimal</code> 1044 */ 1045 private String toString(BigDecimal bd) { 1046 String intString = bd.unscaledValue().toString(); 1047 int scale = bd.scale(); 1048 1049 if (scale == 0) { 1050 return intString; 1051 } 1052 1053 /* Insert decimal point */ 1054 StringBuffer buf; 1055 int insertionPoint = intString.length() - scale; 1056 if (insertionPoint == 0) { /* Point goes right before intVal */ 1057 return "0." + intString; 1058 } else if (insertionPoint > 0) { /* Point goes inside intVal */ 1059 buf = new StringBuffer(intString); 1060 buf.insert(insertionPoint, '.'); 1061 } else { /* We must insert zeros between point and intVal */ 1062 buf = new StringBuffer(3 - insertionPoint + intString.length()); 1063 buf.append("0."); 1064 for (int i = 0; i < -insertionPoint; i++) { 1065 buf.append('0'); 1066 } 1067 buf.append(intString); 1068 } 1069 return buf.toString(); 1070 } 1071 1072 /** 1073 * Checks if a field is set. 1074 * 1075 * A field of a duration object may or may not be present. 1076 * This method can be used to test if a field is present. 1077 * 1078 * @param field 1079 * one of the six Field constants (YEARS,MONTHS,DAYS,HOURS, 1080 * MINUTES, or SECONDS.) 1081 * @return 1082 * true if the field is present. false if not. 1083 * 1084 * @throws NullPointerException 1085 * If the field parameter is null. 1086 */ 1087 public boolean isSet(DatatypeConstants.Field field) { 1088 1089 if (field == null) { 1090 String methodName = "javax.xml.datatype.Duration" + "#isSet(DatatypeConstants.Field field)" ; 1091 throw new NullPointerException( 1092 //"cannot be called with field == null" 1093 DatatypeMessageFormatter.formatMessage(null, "FieldCannotBeNull", new Object[]{methodName}) 1094 ); 1095 } 1096 1097 if (field == DatatypeConstants.YEARS) { 1098 return years != null; 1099 } 1100 1101 if (field == DatatypeConstants.MONTHS) { 1102 return months != null; 1103 } 1104 1105 if (field == DatatypeConstants.DAYS) { 1106 return days != null; 1107 } 1108 1109 if (field == DatatypeConstants.HOURS) { 1110 return hours != null; 1111 } 1112 1113 if (field == DatatypeConstants.MINUTES) { 1114 return minutes != null; 1115 } 1116 1117 if (field == DatatypeConstants.SECONDS) { 1118 return seconds != null; 1119 } 1120 String methodName = "javax.xml.datatype.Duration" + "#isSet(DatatypeConstants.Field field)"; 1121 1122 throw new IllegalArgumentException( 1123 DatatypeMessageFormatter.formatMessage(null,"UnknownField", new Object[]{methodName, field.toString()}) 1124 ); 1125 1126 } 1127 1128 /** 1129 * Gets the value of a field. 1130 * 1131 * Fields of a duration object may contain arbitrary large value. 1132 * Therefore this method is designed to return a {@link Number} object. 1133 * 1134 * In case of YEARS, MONTHS, DAYS, HOURS, and MINUTES, the returned 1135 * number will be a non-negative integer. In case of seconds, 1136 * the returned number may be a non-negative decimal value. 1137 * 1138 * @param field 1139 * one of the six Field constants (YEARS,MONTHS,DAYS,HOURS, 1140 * MINUTES, or SECONDS.) 1141 * @return 1142 * If the specified field is present, this method returns 1143 * a non-null non-negative {@link Number} object that 1144 * represents its value. If it is not present, return null. 1145 * For YEARS, MONTHS, DAYS, HOURS, and MINUTES, this method 1146 * returns a {@link BigInteger} object. For SECONDS, this 1147 * method returns a {@link BigDecimal}. 1148 * 1149 * @throws NullPointerException 1150 * If the field parameter is null. 1151 */ 1152 public Number getField(DatatypeConstants.Field field) { 1153 1154 if (field == null) { 1155 String methodName = "javax.xml.datatype.Duration" + "#isSet(DatatypeConstants.Field field) " ; 1156 1157 throw new NullPointerException( 1158 DatatypeMessageFormatter.formatMessage(null,"FieldCannotBeNull", new Object[]{methodName}) 1159 ); 1160 } 1161 1162 if (field == DatatypeConstants.YEARS) { 1163 return years; 1164 } 1165 1166 if (field == DatatypeConstants.MONTHS) { 1167 return months; 1168 } 1169 1170 if (field == DatatypeConstants.DAYS) { 1171 return days; 1172 } 1173 1174 if (field == DatatypeConstants.HOURS) { 1175 return hours; 1176 } 1177 1178 if (field == DatatypeConstants.MINUTES) { 1179 return minutes; 1180 } 1181 1182 if (field == DatatypeConstants.SECONDS) { 1183 return seconds; 1184 } 1185 /** 1186 throw new IllegalArgumentException( 1187 "javax.xml.datatype.Duration" 1188 + "#(getSet(DatatypeConstants.Field field) called with an unknown field: " 1189 + field.toString() 1190 ); 1191 */ 1192 String methodName = "javax.xml.datatype.Duration" + "#(getSet(DatatypeConstants.Field field)"; 1193 1194 throw new IllegalArgumentException( 1195 DatatypeMessageFormatter.formatMessage(null,"UnknownField", new Object[]{methodName, field.toString()}) 1196 ); 1197 1198 } 1199 1200 /** 1201 * Obtains the value of the YEARS field as an integer value, 1202 * or 0 if not present. 1203 * 1204 * <p> 1205 * This method is a convenience method around the 1206 * {@link #getField(DatatypeConstants.Field)} method. 1207 * 1208 * <p> 1209 * Note that since this method returns <tt>int</tt>, this 1210 * method will return an incorrect value for {@link Duration}s 1211 * with the year field that goes beyond the range of <tt>int</tt>. 1212 * Use <code>getField(YEARS)</code> to avoid possible loss of precision.</p> 1213 * 1214 * @return 1215 * If the YEARS field is present, return 1216 * its value as an integer by using the {@link Number#intValue()} 1217 * method. If the YEARS field is not present, return 0. 1218 */ 1219 public int getYears() { 1220 return getInt(DatatypeConstants.YEARS); 1221 } 1222 1223 /** 1224 * Obtains the value of the MONTHS field as an integer value, 1225 * or 0 if not present. 1226 * 1227 * This method works just like {@link #getYears()} except 1228 * that this method works on the MONTHS field. 1229 * 1230 * @return Months of this <code>Duration</code>. 1231 */ 1232 public int getMonths() { 1233 return getInt(DatatypeConstants.MONTHS); 1234 } 1235 1236 /** 1237 * Obtains the value of the DAYS field as an integer value, 1238 * or 0 if not present. 1239 * 1240 * This method works just like {@link #getYears()} except 1241 * that this method works on the DAYS field. 1242 * 1243 * @return Days of this <code>Duration</code>. 1244 */ 1245 public int getDays() { 1246 return getInt(DatatypeConstants.DAYS); 1247 } 1248 1249 /** 1250 * Obtains the value of the HOURS field as an integer value, 1251 * or 0 if not present. 1252 * 1253 * This method works just like {@link #getYears()} except 1254 * that this method works on the HOURS field. 1255 * 1256 * @return Hours of this <code>Duration</code>. 1257 * 1258 */ 1259 public int getHours() { 1260 return getInt(DatatypeConstants.HOURS); 1261 } 1262 1263 /** 1264 * Obtains the value of the MINUTES field as an integer value, 1265 * or 0 if not present. 1266 * 1267 * This method works just like {@link #getYears()} except 1268 * that this method works on the MINUTES field. 1269 * 1270 * @return Minutes of this <code>Duration</code>. 1271 * 1272 */ 1273 public int getMinutes() { 1274 return getInt(DatatypeConstants.MINUTES); 1275 } 1276 1277 /** 1278 * Obtains the value of the SECONDS field as an integer value, 1279 * or 0 if not present. 1280 * 1281 * This method works just like {@link #getYears()} except 1282 * that this method works on the SECONDS field. 1283 * 1284 * @return seconds in the integer value. The fraction of seconds 1285 * will be discarded (for example, if the actual value is 2.5, 1286 * this method returns 2) 1287 */ 1288 public int getSeconds() { 1289 return getInt(DatatypeConstants.SECONDS); 1290 } 1291 1292 /** 1293 * <p>Return the requested field value as an int.</p> 1294 * 1295 * <p>If field is not set, i.e. == null, 0 is returned.</p> 1296 * 1297 * @param field To get value for. 1298 * 1299 * @return int value of field or 0 if field is not set. 1300 */ 1301 private int getInt(DatatypeConstants.Field field) { 1302 Number n = getField(field); 1303 if (n == null) { 1304 return 0; 1305 } else { 1306 return n.intValue(); 1307 } 1308 } 1309 1310 /** 1311 * <p>Returns the length of the duration in milli-seconds.</p> 1312 * 1313 * <p>If the seconds field carries more digits than milli-second order, 1314 * those will be simply discarded (or in other words, rounded to zero.) 1315 * For example, for any Calendar value <code>x<code>,</p> 1316 * <pre> 1317 * <code>new Duration("PT10.00099S").getTimeInMills(x) == 10000</code>. 1318 * <code>new Duration("-PT10.00099S").getTimeInMills(x) == -10000</code>. 1319 * </pre> 1320 * 1321 * <p> 1322 * Note that this method uses the {@link #addTo(Calendar)} method, 1323 * which may work incorectly with {@link Duration} objects with 1324 * very large values in its fields. See the {@link #addTo(Calendar)} 1325 * method for details. 1326 * 1327 * @param startInstant 1328 * The length of a month/year varies. The <code>startInstant</code> is 1329 * used to disambiguate this variance. Specifically, this method 1330 * returns the difference between <code>startInstant</code> and 1331 * <code>startInstant+duration</code> 1332 * 1333 * @return milliseconds between <code>startInstant</code> and 1334 * <code>startInstant</code> plus this <code>Duration</code> 1335 * 1336 * @throws NullPointerException if <code>startInstant</code> parameter 1337 * is null. 1338 * 1339 */ 1340 public long getTimeInMillis(final Calendar startInstant) { 1341 Calendar cal = (Calendar) startInstant.clone(); 1342 addTo(cal); 1343 return getCalendarTimeInMillis(cal) 1344 - getCalendarTimeInMillis(startInstant); 1345 } 1346 1347 /** 1348 * <p>Returns the length of the duration in milli-seconds.</p> 1349 * 1350 * <p>If the seconds field carries more digits than milli-second order, 1351 * those will be simply discarded (or in other words, rounded to zero.) 1352 * For example, for any <code>Date</code> value <code>x<code>,</p> 1353 * <pre> 1354 * <code>new Duration("PT10.00099S").getTimeInMills(x) == 10000</code>. 1355 * <code>new Duration("-PT10.00099S").getTimeInMills(x) == -10000</code>. 1356 * </pre> 1357 * 1358 * <p> 1359 * Note that this method uses the {@link #addTo(Date)} method, 1360 * which may work incorectly with {@link Duration} objects with 1361 * very large values in its fields. See the {@link #addTo(Date)} 1362 * method for details. 1363 * 1364 * @param startInstant 1365 * The length of a month/year varies. The <code>startInstant</code> is 1366 * used to disambiguate this variance. Specifically, this method 1367 * returns the difference between <code>startInstant</code> and 1368 * <code>startInstant+duration</code>. 1369 * 1370 * @throws NullPointerException 1371 * If the startInstant parameter is null. 1372 * 1373 * @return milliseconds between <code>startInstant</code> and 1374 * <code>startInstant</code> plus this <code>Duration</code> 1375 * 1376 * @see #getTimeInMillis(Calendar) 1377 */ 1378 public long getTimeInMillis(final Date startInstant) { 1379 Calendar cal = new GregorianCalendar(); 1380 cal.setTime(startInstant); 1381 this.addTo(cal); 1382 return getCalendarTimeInMillis(cal) - startInstant.getTime(); 1383 } 1384 1385 // /** 1386 // * Returns an equivalent but "normalized" duration value. 1387 // * 1388 // * Intuitively, the normalization moves YEARS into 1389 // * MONTHS (by x12) and moves DAYS, HOURS, and MINUTES fields 1390 // * into SECONDS (by x86400, x3600, and x60 respectively.) 1391 // * 1392 // * 1393 // * Formally, this method satisfies the following conditions: 1394 // * <ul> 1395 // * <li>x.normalize().equals(x) 1396 // * <li>!x.normalize().isSet(Duration.YEARS) 1397 // * <li>!x.normalize().isSet(Duration.DAYS) 1398 // * <li>!x.normalize().isSet(Duration.HOURS) 1399 // * <li>!x.normalize().isSet(Duration.MINUTES) 1400 // * </ul> 1401 // * 1402 // * @return 1403 // * always return a non-null valid value. 1404 // */ 1405 // public Duration normalize() { 1406 // return null; 1407 // } 1408 1409 /** 1410 * <p>Converts the years and months fields into the days field 1411 * by using a specific time instant as the reference point.</p> 1412 * 1413 * <p>For example, duration of one month normalizes to 31 days 1414 * given the start time instance "July 8th 2003, 17:40:32".</p> 1415 * 1416 * <p>Formally, the computation is done as follows:</p> 1417 * <ol> 1418 * <li>The given Calendar object is cloned. 1419 * <li>The years, months and days fields will be added to 1420 * the {@link Calendar} object 1421 * by using the {@link Calendar#add(int,int)} method. 1422 * <li>The difference between two Calendars are computed in terms of days. 1423 * <li>The computed days, along with the hours, minutes and seconds 1424 * fields of this duration object is used to construct a new 1425 * Duration object. 1426 * </ol> 1427 * 1428 * <p>Note that since the Calendar class uses <code>int</code> to 1429 * hold the value of year and month, this method may produce 1430 * an unexpected result if this duration object holds 1431 * a very large value in the years or months fields.</p> 1432 * 1433 * @param startTimeInstant <code>Calendar</code> reference point. 1434 * 1435 * @return <code>Duration</code> of years and months of this <code>Duration</code> as days. 1436 * 1437 * @throws NullPointerException If the startTimeInstant parameter is null. 1438 */ 1439 public Duration normalizeWith(Calendar startTimeInstant) { 1440 1441 Calendar c = (Calendar) startTimeInstant.clone(); 1442 1443 // using int may cause overflow, but 1444 // Calendar internally treats value as int anyways. 1445 c.add(Calendar.YEAR, getYears() * signum); 1446 c.add(Calendar.MONTH, getMonths() * signum); 1447 c.add(Calendar.DAY_OF_MONTH, getDays() * signum); 1448 1449 // obtain the difference in terms of days 1450 long diff = getCalendarTimeInMillis(c) - getCalendarTimeInMillis(startTimeInstant); 1451 int days = (int) (diff / (1000L * 60L * 60L * 24L)); 1452 1453 return new DurationImpl( 1454 days >= 0, 1455 null, 1456 null, 1457 wrap(Math.abs(days)), 1458 (BigInteger) getField(DatatypeConstants.HOURS), 1459 (BigInteger) getField(DatatypeConstants.MINUTES), 1460 (BigDecimal) getField(DatatypeConstants.SECONDS)); 1461 } 1462 1463 /** 1464 * <p>Computes a new duration whose value is <code>factor</code> times 1465 * longer than the value of this duration.</p> 1466 * 1467 * <p>This method is provided for the convenience. 1468 * It is functionally equivalent to the following code:</p> 1469 * <pre> 1470 * multiply(new BigDecimal(String.valueOf(factor))) 1471 * </pre> 1472 * 1473 * @param factor Factor times longer of new <code>Duration</code> to create. 1474 * 1475 * @return New <code>Duration</code> that is <code>factor</code>times longer than this <code>Duration</code>. 1476 * 1477 * @see #multiply(BigDecimal) 1478 */ 1479 public Duration multiply(int factor) { 1480 return multiply(BigDecimal.valueOf(factor)); 1481 } 1482 1483 /** 1484 * Computes a new duration whose value is <code>factor</code> times 1485 * longer than the value of this duration. 1486 * 1487 * <p> 1488 * For example, 1489 * <pre> 1490 * "P1M" (1 month) * "12" = "P12M" (12 months) 1491 * "PT1M" (1 min) * "0.3" = "PT18S" (18 seconds) 1492 * "P1M" (1 month) * "1.5" = IllegalStateException 1493 * </pre> 1494 * 1495 * <p> 1496 * Since the {@link Duration} class is immutable, this method 1497 * doesn't change the value of this object. It simply computes 1498 * a new Duration object and returns it. 1499 * 1500 * <p> 1501 * The operation will be performed field by field with the precision 1502 * of {@link BigDecimal}. Since all the fields except seconds are 1503 * restricted to hold integers, 1504 * any fraction produced by the computation will be 1505 * carried down toward the next lower unit. For example, 1506 * if you multiply "P1D" (1 day) with "0.5", then it will be 0.5 day, 1507 * which will be carried down to "PT12H" (12 hours). 1508 * When fractions of month cannot be meaningfully carried down 1509 * to days, or year to months, this will cause an 1510 * {@link IllegalStateException} to be thrown. 1511 * For example if you multiple one month by 0.5.</p> 1512 * 1513 * <p> 1514 * To avoid {@link IllegalStateException}, use 1515 * the {@link #normalizeWith(Calendar)} method to remove the years 1516 * and months fields. 1517 * 1518 * @param factor to multiply by 1519 * 1520 * @return 1521 * returns a non-null valid {@link Duration} object 1522 * 1523 * @throws IllegalStateException if operation produces fraction in 1524 * the months field. 1525 * 1526 * @throws NullPointerException if the <code>factor</code> parameter is 1527 * <code>null</code>. 1528 * 1529 */ 1530 public Duration multiply(BigDecimal factor) { 1531 BigDecimal carry = ZERO; 1532 int factorSign = factor.signum(); 1533 factor = factor.abs(); 1534 1535 BigDecimal[] buf = new BigDecimal[6]; 1536 1537 for (int i = 0; i < 5; i++) { 1538 BigDecimal bd = getFieldAsBigDecimal(FIELDS[i]); 1539 bd = bd.multiply(factor).add(carry); 1540 1541 buf[i] = bd.setScale(0, BigDecimal.ROUND_DOWN); 1542 1543 bd = bd.subtract(buf[i]); 1544 if (i == 1) { 1545 if (bd.signum() != 0) { 1546 throw new IllegalStateException(); // illegal carry-down 1547 } else { 1548 carry = ZERO; 1549 } 1550 } else { 1551 carry = bd.multiply(FACTORS[i]); 1552 } 1553 } 1554 1555 if (seconds != null) { 1556 buf[5] = seconds.multiply(factor).add(carry); 1557 } else { 1558 buf[5] = carry; 1559 } 1560 1561 return new DurationImpl( 1562 this.signum * factorSign >= 0, 1563 toBigInteger(buf[0], null == years), 1564 toBigInteger(buf[1], null == months), 1565 toBigInteger(buf[2], null == days), 1566 toBigInteger(buf[3], null == hours), 1567 toBigInteger(buf[4], null == minutes), 1568 (buf[5].signum() == 0 && seconds == null) ? null : buf[5]); 1569 } 1570 1571 /** 1572 * <p>Gets the value of the field as a {@link BigDecimal}.</p> 1573 * 1574 * <p>If the field is unset, return 0.</p> 1575 * 1576 * @param f Field to get value for. 1577 * 1578 * @return non-null valid {@link BigDecimal}. 1579 */ 1580 private BigDecimal getFieldAsBigDecimal(DatatypeConstants.Field f) { 1581 if (f == DatatypeConstants.SECONDS) { 1582 if (seconds != null) { 1583 return seconds; 1584 } else { 1585 return ZERO; 1586 } 1587 } else { 1588 BigInteger bi = (BigInteger) getField(f); 1589 if (bi == null) { 1590 return ZERO; 1591 } else { 1592 return new BigDecimal(bi); 1593 } 1594 } 1595 } 1596 1597 /** 1598 * <p>BigInteger value of BigDecimal value.</p> 1599 * 1600 * @param value Value to convert. 1601 * @param canBeNull Can returned value be null? 1602 * 1603 * @return BigInteger value of BigDecimal, possibly null. 1604 */ 1605 private static BigInteger toBigInteger( 1606 BigDecimal value, 1607 boolean canBeNull) { 1608 if (canBeNull && value.signum() == 0) { 1609 return null; 1610 } else { 1611 return value.unscaledValue(); 1612 } 1613 } 1614 1615 /** 1616 * 1 unit of FIELDS[i] is equivalent to <code>FACTORS[i]</code> unit of 1617 * FIELDS[i+1]. 1618 */ 1619 private static final BigDecimal[] FACTORS = new BigDecimal[]{ 1620 BigDecimal.valueOf(12), 1621 null/*undefined*/, 1622 BigDecimal.valueOf(24), 1623 BigDecimal.valueOf(60), 1624 BigDecimal.valueOf(60) 1625 }; 1626 1627 /** 1628 * <p>Computes a new duration whose value is <code>this+rhs</code>.</p> 1629 * 1630 * <p>For example,</p> 1631 * <pre> 1632 * "1 day" + "-3 days" = "-2 days" 1633 * "1 year" + "1 day" = "1 year and 1 day" 1634 * "-(1 hour,50 minutes)" + "-20 minutes" = "-(1 hours,70 minutes)" 1635 * "15 hours" + "-3 days" = "-(2 days,9 hours)" 1636 * "1 year" + "-1 day" = IllegalStateException 1637 * </pre> 1638 * 1639 * <p>Since there's no way to meaningfully subtract 1 day from 1 month, 1640 * there are cases where the operation fails in 1641 * {@link IllegalStateException}.</p> 1642 * 1643 * <p> 1644 * Formally, the computation is defined as follows.</p> 1645 * <p> 1646 * Firstly, we can assume that two {@link Duration}s to be added 1647 * are both positive without losing generality (i.e., 1648 * <code>(-X)+Y=Y-X</code>, <code>X+(-Y)=X-Y</code>, 1649 * <code>(-X)+(-Y)=-(X+Y)</code>) 1650 * 1651 * <p> 1652 * Addition of two positive {@link Duration}s are simply defined as 1653 * field by field addition where missing fields are treated as 0. 1654 * <p> 1655 * A field of the resulting {@link Duration} will be unset if and 1656 * only if respective fields of two input {@link Duration}s are unset. 1657 * <p> 1658 * Note that <code>lhs.add(rhs)</code> will be always successful if 1659 * <code>lhs.signum()*rhs.signum()!=-1</code> or both of them are 1660 * normalized.</p> 1661 * 1662 * @param rhs <code>Duration</code> to add to this <code>Duration</code> 1663 * 1664 * @return 1665 * non-null valid Duration object. 1666 * 1667 * @throws NullPointerException 1668 * If the rhs parameter is null. 1669 * @throws IllegalStateException 1670 * If two durations cannot be meaningfully added. For 1671 * example, adding negative one day to one month causes 1672 * this exception. 1673 * 1674 * 1675 * @see #subtract(Duration) 1676 */ 1677 public Duration add(final Duration rhs) { 1678 Duration lhs = this; 1679 BigDecimal[] buf = new BigDecimal[6]; 1680 1681 buf[0] = sanitize((BigInteger) lhs.getField(DatatypeConstants.YEARS), 1682 lhs.getSign()).add(sanitize((BigInteger) rhs.getField(DatatypeConstants.YEARS), rhs.getSign())); 1683 buf[1] = sanitize((BigInteger) lhs.getField(DatatypeConstants.MONTHS), 1684 lhs.getSign()).add(sanitize((BigInteger) rhs.getField(DatatypeConstants.MONTHS), rhs.getSign())); 1685 buf[2] = sanitize((BigInteger) lhs.getField(DatatypeConstants.DAYS), 1686 lhs.getSign()).add(sanitize((BigInteger) rhs.getField(DatatypeConstants.DAYS), rhs.getSign())); 1687 buf[3] = sanitize((BigInteger) lhs.getField(DatatypeConstants.HOURS), 1688 lhs.getSign()).add(sanitize((BigInteger) rhs.getField(DatatypeConstants.HOURS), rhs.getSign())); 1689 buf[4] = sanitize((BigInteger) lhs.getField(DatatypeConstants.MINUTES), 1690 lhs.getSign()).add(sanitize((BigInteger) rhs.getField(DatatypeConstants.MINUTES), rhs.getSign())); 1691 buf[5] = sanitize((BigDecimal) lhs.getField(DatatypeConstants.SECONDS), 1692 lhs.getSign()).add(sanitize((BigDecimal) rhs.getField(DatatypeConstants.SECONDS), rhs.getSign())); 1693 1694 // align sign 1695 alignSigns(buf, 0, 2); // Y,M 1696 alignSigns(buf, 2, 6); // D,h,m,s 1697 1698 // make sure that the sign bit is consistent across all 6 fields. 1699 int s = 0; 1700 for (int i = 0; i < 6; i++) { 1701 if (s * buf[i].signum() < 0) { 1702 throw new IllegalStateException(); 1703 } 1704 if (s == 0) { 1705 s = buf[i].signum(); 1706 } 1707 } 1708 1709 return new DurationImpl( 1710 s >= 0, 1711 toBigInteger(sanitize(buf[0], s), 1712 lhs.getField(DatatypeConstants.YEARS) == null && rhs.getField(DatatypeConstants.YEARS) == null), 1713 toBigInteger(sanitize(buf[1], s), 1714 lhs.getField(DatatypeConstants.MONTHS) == null && rhs.getField(DatatypeConstants.MONTHS) == null), 1715 toBigInteger(sanitize(buf[2], s), 1716 lhs.getField(DatatypeConstants.DAYS) == null && rhs.getField(DatatypeConstants.DAYS) == null), 1717 toBigInteger(sanitize(buf[3], s), 1718 lhs.getField(DatatypeConstants.HOURS) == null && rhs.getField(DatatypeConstants.HOURS) == null), 1719 toBigInteger(sanitize(buf[4], s), 1720 lhs.getField(DatatypeConstants.MINUTES) == null && rhs.getField(DatatypeConstants.MINUTES) == null), 1721 (buf[5].signum() == 0 1722 && lhs.getField(DatatypeConstants.SECONDS) == null 1723 && rhs.getField(DatatypeConstants.SECONDS) == null) ? null : sanitize(buf[5], s)); 1724 } 1725 1726 private static void alignSigns(BigDecimal[] buf, int start, int end) { 1727 // align sign 1728 boolean touched; 1729 1730 do { // repeat until all the sign bits become consistent 1731 touched = false; 1732 int s = 0; // sign of the left fields 1733 1734 for (int i = start; i < end; i++) { 1735 if (s * buf[i].signum() < 0) { 1736 // this field has different sign than its left field. 1737 touched = true; 1738 1739 // compute the number of unit that needs to be borrowed. 1740 BigDecimal borrow = 1741 buf[i].abs().divide( 1742 FACTORS[i - 1], 1743 BigDecimal.ROUND_UP); 1744 if (buf[i].signum() > 0) { 1745 borrow = borrow.negate(); 1746 } 1747 1748 // update values 1749 buf[i - 1] = buf[i - 1].subtract(borrow); 1750 buf[i] = buf[i].add(borrow.multiply(FACTORS[i - 1])); 1751 } 1752 if (buf[i].signum() != 0) { 1753 s = buf[i].signum(); 1754 } 1755 } 1756 } while (touched); 1757 } 1758 1759 /** 1760 * Compute <code>value*signum</code> where value==null is treated as 1761 * value==0. 1762 * @param value Value to sanitize. 1763 * @param signum 0 to sanitize to 0, > 0 to sanitize to <code>value</code>, < 0 to sanitize to negative <code>value</code>. 1764 * 1765 * @return non-null {@link BigDecimal}. 1766 */ 1767 private static BigDecimal sanitize(BigInteger value, int signum) { 1768 if (signum == 0 || value == null) { 1769 return ZERO; 1770 } 1771 if (signum > 0) { 1772 return new BigDecimal(value); 1773 } 1774 return new BigDecimal(value.negate()); 1775 } 1776 1777 /** 1778 * <p>Compute <code>value*signum</code> where <code>value==null</code> is treated as <code>value==0</code></p>. 1779 * 1780 * @param value Value to sanitize. 1781 * @param signum 0 to sanitize to 0, > 0 to sanitize to <code>value</code>, < 0 to sanitize to negative <code>value</code>. 1782 * 1783 * @return non-null {@link BigDecimal}. 1784 */ 1785 static BigDecimal sanitize(BigDecimal value, int signum) { 1786 if (signum == 0 || value == null) { 1787 return ZERO; 1788 } 1789 if (signum > 0) { 1790 return value; 1791 } 1792 return value.negate(); 1793 } 1794 1795 /** 1796 * <p>Computes a new duration whose value is <code>this-rhs</code>.</p> 1797 * 1798 * <p>For example:</p> 1799 * <pre> 1800 * "1 day" - "-3 days" = "4 days" 1801 * "1 year" - "1 day" = IllegalStateException 1802 * "-(1 hour,50 minutes)" - "-20 minutes" = "-(1hours,30 minutes)" 1803 * "15 hours" - "-3 days" = "3 days and 15 hours" 1804 * "1 year" - "-1 day" = "1 year and 1 day" 1805 * </pre> 1806 * 1807 * <p>Since there's no way to meaningfully subtract 1 day from 1 month, 1808 * there are cases where the operation fails in {@link IllegalStateException}.</p> 1809 * 1810 * <p>Formally the computation is defined as follows. 1811 * First, we can assume that two {@link Duration}s are both positive 1812 * without losing generality. (i.e., 1813 * <code>(-X)-Y=-(X+Y)</code>, <code>X-(-Y)=X+Y</code>, 1814 * <code>(-X)-(-Y)=-(X-Y)</code>)</p> 1815 * 1816 * <p>Then two durations are subtracted field by field. 1817 * If the sign of any non-zero field <tt>F</tt> is different from 1818 * the sign of the most significant field, 1819 * 1 (if <tt>F</tt> is negative) or -1 (otherwise) 1820 * will be borrowed from the next bigger unit of <tt>F</tt>.</p> 1821 * 1822 * <p>This process is repeated until all the non-zero fields have 1823 * the same sign.</p> 1824 * 1825 * <p>If a borrow occurs in the days field (in other words, if 1826 * the computation needs to borrow 1 or -1 month to compensate 1827 * days), then the computation fails by throwing an 1828 * {@link IllegalStateException}.</p> 1829 * 1830 * @param rhs <code>Duration</code> to substract from this <code>Duration</code>. 1831 * 1832 * @return New <code>Duration</code> created from subtracting <code>rhs</code> from this <code>Duration</code>. 1833 * 1834 * @throws IllegalStateException 1835 * If two durations cannot be meaningfully subtracted. For 1836 * example, subtracting one day from one month causes 1837 * this exception. 1838 * 1839 * @throws NullPointerException 1840 * If the rhs parameter is null. 1841 * 1842 * @see #add(Duration) 1843 */ 1844 public Duration subtract(final Duration rhs) { 1845 return add(rhs.negate()); 1846 } 1847 1848 /** 1849 * Returns a new {@link Duration} object whose 1850 * value is <code>-this</code>. 1851 * 1852 * <p> 1853 * Since the {@link Duration} class is immutable, this method 1854 * doesn't change the value of this object. It simply computes 1855 * a new Duration object and returns it. 1856 * 1857 * @return 1858 * always return a non-null valid {@link Duration} object. 1859 */ 1860 public Duration negate() { 1861 return new DurationImpl( 1862 signum <= 0, 1863 years, 1864 months, 1865 days, 1866 hours, 1867 minutes, 1868 seconds); 1869 } 1870 1871 /** 1872 * Returns the sign of this duration in -1,0, or 1. 1873 * 1874 * @return 1875 * -1 if this duration is negative, 0 if the duration is zero, 1876 * and 1 if the duration is postive. 1877 */ 1878 public int signum() { 1879 return signum; 1880 } 1881 1882 1883 /** 1884 * Adds this duration to a {@link Calendar} object. 1885 * 1886 * <p> 1887 * Calls {@link java.util.Calendar#add(int,int)} in the 1888 * order of YEARS, MONTHS, DAYS, HOURS, MINUTES, SECONDS, and MILLISECONDS 1889 * if those fields are present. Because the {@link Calendar} class 1890 * uses int to hold values, there are cases where this method 1891 * won't work correctly (for example if values of fields 1892 * exceed the range of int.) 1893 * </p> 1894 * 1895 * <p> 1896 * Also, since this duration class is a Gregorian duration, this 1897 * method will not work correctly if the given {@link Calendar} 1898 * object is based on some other calendar systems. 1899 * </p> 1900 * 1901 * <p> 1902 * Any fractional parts of this {@link Duration} object 1903 * beyond milliseconds will be simply ignored. For example, if 1904 * this duration is "P1.23456S", then 1 is added to SECONDS, 1905 * 234 is added to MILLISECONDS, and the rest will be unused. 1906 * </p> 1907 * 1908 * <p> 1909 * Note that because {@link Calendar#add(int, int)} is using 1910 * <tt>int</tt>, {@link Duration} with values beyond the 1911 * range of <tt>int</tt> in its fields 1912 * will cause overflow/underflow to the given {@link Calendar}. 1913 * {@link XMLGregorianCalendar#add(Duration)} provides the same 1914 * basic operation as this method while avoiding 1915 * the overflow/underflow issues. 1916 * 1917 * @param calendar 1918 * A calendar object whose value will be modified. 1919 * @throws NullPointerException 1920 * if the calendar parameter is null. 1921 */ 1922 public void addTo(Calendar calendar) { 1923 calendar.add(Calendar.YEAR, getYears() * signum); 1924 calendar.add(Calendar.MONTH, getMonths() * signum); 1925 calendar.add(Calendar.DAY_OF_MONTH, getDays() * signum); 1926 calendar.add(Calendar.HOUR, getHours() * signum); 1927 calendar.add(Calendar.MINUTE, getMinutes() * signum); 1928 calendar.add(Calendar.SECOND, getSeconds() * signum); 1929 1930 if (seconds != null) { 1931 BigDecimal fraction = 1932 seconds.subtract(seconds.setScale(0, BigDecimal.ROUND_DOWN)); 1933 int millisec = fraction.movePointRight(3).intValue(); 1934 calendar.add(Calendar.MILLISECOND, millisec * signum); 1935 } 1936 } 1937 1938 /** 1939 * Adds this duration to a {@link Date} object. 1940 * 1941 * <p> 1942 * The given date is first converted into 1943 * a {@link java.util.GregorianCalendar}, then the duration 1944 * is added exactly like the {@link #addTo(Calendar)} method. 1945 * 1946 * <p> 1947 * The updated time instant is then converted back into a 1948 * {@link Date} object and used to update the given {@link Date} object. 1949 * 1950 * <p> 1951 * This somewhat redundant computation is necessary to unambiguously 1952 * determine the duration of months and years. 1953 * 1954 * @param date 1955 * A date object whose value will be modified. 1956 * @throws NullPointerException 1957 * if the date parameter is null. 1958 */ 1959 public void addTo(Date date) { 1960 Calendar cal = new GregorianCalendar(); 1961 cal.setTime(date); // this will throw NPE if date==null 1962 this.addTo(cal); 1963 date.setTime(getCalendarTimeInMillis(cal)); 1964 } 1965 1966 /** 1967 * <p>Stream Unique Identifier.</p> 1968 * 1969 * <p>TODO: Serialization should use the XML string representation as 1970 * the serialization format to ensure future compatibility.</p> 1971 */ 1972 private static final long serialVersionUID = 1L; 1973 1974 /** 1975 * Writes {@link Duration} as a lexical representation 1976 * for maximum future compatibility. 1977 * 1978 * @return 1979 * An object that encapsulates the string 1980 * returned by <code>this.toString()</code>. 1981 */ 1982 private Object writeReplace() throws IOException { 1983 return new DurationStream(this.toString()); 1984 } 1985 1986 /** 1987 * Representation of {@link Duration} in the object stream. 1988 * 1989 * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com) 1990 */ 1991 private static class DurationStream implements Serializable { 1992 private final String lexical; 1993 1994 private DurationStream(String _lexical) { 1995 this.lexical = _lexical; 1996 } 1997 1998 private Object readResolve() throws ObjectStreamException { 1999 // try { 2000 return new DurationImpl(lexical); 2001 // } catch( ParseException e ) { 2002 // throw new StreamCorruptedException("unable to parse "+lexical+" as duration"); 2003 // } 2004 } 2005 2006 private static final long serialVersionUID = 1L; 2007 } 2008 2009 /** 2010 * Calls the {@link Calendar#getTimeInMillis} method. 2011 * Prior to JDK1.4, this method was protected and therefore 2012 * cannot be invoked directly. 2013 * 2014 * In future, this should be replaced by 2015 * <code>cal.getTimeInMillis()</code> 2016 */ 2017 private static long getCalendarTimeInMillis(Calendar cal) { 2018 return cal.getTime().getTime(); 2019 } 2020 }