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