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