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