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&lt;B (A is shorter than B)
  61  *  <li>A&gt;B (A is longer than B)
  62  *  <li>A==B   (A and B are of the same duration)
  63  *  <li>A&lt;>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 }