1 /*
2 * reserved comment block
3 * DO NOT REMOVE OR ALTER!
4 */
5 /*
6 * Copyright 2005 The Apache Software Foundation.
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20
21 package com.sun.org.apache.xerces.internal.jaxp.datatype;
22
23 import java.io.IOException;
24 import java.io.ObjectStreamException;
25 import java.io.Serializable;
26 import java.math.BigDecimal;
27 import java.math.BigInteger;
28 import java.util.Calendar;
29 import java.util.Date;
30 import java.util.GregorianCalendar;
31 import java.util.TimeZone;
89 *
90 *
91 * <h2>Range of allowed values</h2>
92 * <p>
93 * Because some operations of {@link Duration} rely on {@link Calendar}
94 * even though {@link Duration} can hold very large or very small values,
95 * some of the methods may not work correctly on such {@link Duration}s.
96 * The impacted methods document their dependency on {@link Calendar}.
97 *
98 *
99 * @author <a href="mailto:Kohsuke.Kawaguchi@Sun.com">Kohsuke Kawaguchi</a>
100 * @author <a href="mailto:Joseph.Fialli@Sun.com">Joseph Fialli</a>
101 * @version $Revision: 1.8 $, $Date: 2010/05/19 23:20:06 $
102
103 * @see XMLGregorianCalendar#add(Duration)
104 */
105 class DurationImpl
106 extends Duration
107 implements Serializable {
108
109 /**
110 * <p>Number of Fields.</p>
111 */
112 private static final int FIELD_NUM = 6;
113
114 /**
115 * <p>Internal array of value Fields.</p>
116 */
117 private static final DatatypeConstants.Field[] FIELDS = new DatatypeConstants.Field[]{
118 DatatypeConstants.YEARS,
119 DatatypeConstants.MONTHS,
120 DatatypeConstants.DAYS,
121 DatatypeConstants.HOURS,
122 DatatypeConstants.MINUTES,
123 DatatypeConstants.SECONDS
124 };
125
126 /**
127 * <p>Internal array of value Field ids.</p>
128 */
129 private static final int[] FIELD_IDS = {
130 DatatypeConstants.YEARS.getId(),
131 DatatypeConstants.MONTHS.getId(),
132 DatatypeConstants.DAYS.getId(),
133 DatatypeConstants.HOURS.getId(),
134 DatatypeConstants.MINUTES.getId(),
135 DatatypeConstants.SECONDS.getId()
136 };
137
138 /**
139 * TimeZone for GMT.
140 */
141 private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
142
143 /**
144 * <p>BigDecimal value of 0.</p>
145 */
146 private static final BigDecimal ZERO = BigDecimal.valueOf((long) 0);
147
148 /**
149 * <p>Indicates the sign. -1, 0 or 1 if the duration is negative,
150 * zero, or positive.</p>
151 */
152 protected int signum;
153
154 /**
155 * <p>Years of this <code>Duration</code>.</p>
156 */
157 /**
158 * These were final since Duration is immutable. But new subclasses need
159 * to be able to set after conversion. It won't break the immutable nature
160 * of them since there's no other way to set new values to them
161 */
162 protected BigInteger years;
163
164 /**
165 * <p>Months of this <code>Duration</code>.</p>
166 */
199 }
200
201 /**
202 * TODO: Javadoc
203 * @param isPositive Sign.
204 *
205 * @return 1 if positive, else -1.
206 */
207 protected int calcSignum(boolean isPositive) {
208 if ((years == null || years.signum() == 0)
209 && (months == null || months.signum() == 0)
210 && (days == null || days.signum() == 0)
211 && (hours == null || hours.signum() == 0)
212 && (minutes == null || minutes.signum() == 0)
213 && (seconds == null || seconds.signum() == 0)) {
214 return 0;
215 }
216
217 if (isPositive) {
218 return 1;
219 } else {
220 return -1;
221 }
222
223 }
224
225 /**
226 * <p>Constructs a new Duration object by specifying each field individually.</p>
227 *
228 * <p>All the parameters are optional as long as at least one field is present.
229 * If specified, parameters have to be zero or positive.</p>
230 *
231 * @param isPositive Set to <code>false</code> to create a negative duration. When the length
232 * of the duration is zero, this parameter will be ignored.
233 * @param years of this <code>Duration</code>
234 * @param months of this <code>Duration</code>
235 * @param days of this <code>Duration</code>
236 * @param hours of this <code>Duration</code>
237 * @param minutes of this <code>Duration</code>
238 * @param seconds of this <code>Duration</code>
239 *
240 * @throws IllegalArgumentException
241 * If years, months, days, hours, minutes and
242 * seconds parameters are all <code>null</code>. Or if any
340 wrap(hours),
341 wrap(minutes),
342 seconds != DatatypeConstants.FIELD_UNDEFINED ? new BigDecimal(String.valueOf(seconds)) : null);
343 }
344
345 /**
346 * TODO: Javadoc
347 *
348 * @param i int to convert to BigInteger.
349 *
350 * @return BigInteger representation of int.
351 */
352 protected static BigInteger wrap(final int i) {
353
354 // field may not be set
355 if (i == DatatypeConstants.FIELD_UNDEFINED) {
356 return null;
357 }
358
359 // int -> BigInteger
360 return new BigInteger(String.valueOf(i));
361 }
362
363 /**
364 * <p>Constructs a new Duration object by specifying the duration
365 * in milliseconds.</p>
366 *
367 * @param durationInMilliSeconds
368 * The length of the duration in milliseconds.
369 */
370 protected DurationImpl(final long durationInMilliSeconds) {
371
372 long l = durationInMilliSeconds;
373
374 if (l > 0) {
375 signum = 1;
376 } else if (l < 0) {
377 signum = -1;
378 if (l == 0x8000000000000000L) {
379 // negating 0x8000000000000000L causes an overflow
380 l++;
381 }
382 l *= -1;
383 } else {
384 signum = 0;
385 }
386
387 // let GregorianCalendar do the heavy lifting
388 GregorianCalendar gregorianCalendar = new GregorianCalendar(GMT);
389
390 // duration is the offset from the Epoch
391 gregorianCalendar.setTimeInMillis(l);
392
393 // now find out how much each field has changed
394 long int2long = 0L;
395
396 // years
397 int2long = gregorianCalendar.get(Calendar.YEAR) - 1970;
398 this.years = BigInteger.valueOf(int2long);
399
400 // months
401 int2long = gregorianCalendar.get(Calendar.MONTH);
402 this.months = BigInteger.valueOf(int2long);
403
437 * the following holds for any lexically correct string x:
438 * <pre>
439 * new Duration(x).toString().equals(x)
440 * </pre>
441 *
442 * Returns a non-null valid duration object that holds the value
443 * indicated by the lexicalRepresentation parameter.
444 *
445 * @param lexicalRepresentation
446 * Lexical representation of a duration.
447 * @throws IllegalArgumentException
448 * If the given string does not conform to the aforementioned
449 * specification.
450 * @throws NullPointerException
451 * If the given string is null.
452 */
453 protected DurationImpl(String lexicalRepresentation)
454 throws IllegalArgumentException {
455 // only if I could use the JDK1.4 regular expression ....
456
457 final String s = lexicalRepresentation;
458 boolean positive;
459 int[] idx = new int[1];
460 int length = s.length();
461 boolean timeRequired = false;
462
463 if (lexicalRepresentation == null) {
464 throw new NullPointerException();
465 }
466
467 idx[0] = 0;
468 if (length != idx[0] && s.charAt(idx[0]) == '-') {
469 idx[0]++;
470 positive = false;
471 } else {
472 positive = true;
473 }
474
475 if (length != idx[0] && s.charAt(idx[0]++) != 'P') {
476 throw new IllegalArgumentException(s); //,idx[0]-1);
477 }
478
479
480 // phase 1: chop the string into chunks
481 // (where a chunk is '<number><a symbol>'
482 //--------------------------------------
483 int dateLen = 0;
484 String[] dateParts = new String[3];
485 int[] datePartsIndex = new int[3];
486 while (length != idx[0]
487 && isDigit(s.charAt(idx[0]))
488 && dateLen < 3) {
489 datePartsIndex[dateLen] = idx[0];
490 dateParts[dateLen++] = parsePiece(s, idx);
491 }
492
493 if (length != idx[0]) {
494 if (s.charAt(idx[0]++) == 'T') {
495 timeRequired = true;
496 } else {
497 throw new IllegalArgumentException(s); // ,idx[0]-1);
498 }
499 }
500
501 int timeLen = 0;
502 String[] timeParts = new String[3];
503 int[] timePartsIndex = new int[3];
504 while (length != idx[0]
505 && isDigitOrPeriod(s.charAt(idx[0]))
506 && timeLen < 3) {
507 timePartsIndex[timeLen] = idx[0];
508 timeParts[timeLen++] = parsePiece(s, idx);
509 }
510
511 if (timeRequired && timeLen == 0) {
512 throw new IllegalArgumentException(s); // ,idx[0]);
513 }
514
515 if (length != idx[0]) {
516 throw new IllegalArgumentException(s); // ,idx[0]);
587 * TODO: Javadoc.
588 *
589 * @param whole TODO: ???
590 * @param parts TODO: ???
591 * @param partsIndex TODO: ???
592 * @param len TODO: ???
593 * @param tokens TODO: ???
594 *
595 * @throws IllegalArgumentException TODO: ???
596 */
597 private static void organizeParts(
598 String whole,
599 String[] parts,
600 int[] partsIndex,
601 int len,
602 String tokens)
603 throws IllegalArgumentException {
604
605 int idx = tokens.length();
606 for (int i = len - 1; i >= 0; i--) {
607 int nidx =
608 tokens.lastIndexOf(
609 parts[i].charAt(parts[i].length() - 1),
610 idx - 1);
611 if (nidx == -1) {
612 throw new IllegalArgumentException(whole);
613 // ,partsIndex[i]+parts[i].length()-1);
614 }
615
616 for (int j = nidx + 1; j < idx; j++) {
617 parts[j] = null;
618 }
619 idx = nidx;
620 parts[idx] = parts[i];
621 partsIndex[idx] = partsIndex[i];
622 }
623 for (idx--; idx >= 0; idx--) {
624 parts[idx] = null;
625 }
626 }
705 * <li>{@link DatatypeConstants#INDETERMINATE} if a conclusive partial order relation cannot be determined</li>
706 * </ul>
707 *
708 * @param duration to compare
709 *
710 * @return the relationship between <code>this</code> <code>Duration</code>and <code>duration</code> parameter as
711 * {@link DatatypeConstants#LESSER}, {@link DatatypeConstants#EQUAL}, {@link DatatypeConstants#GREATER}
712 * or {@link DatatypeConstants#INDETERMINATE}.
713 *
714 * @throws UnsupportedOperationException If the underlying implementation
715 * cannot reasonably process the request, e.g. W3C XML Schema allows for
716 * arbitrarily large/small/precise values, the request may be beyond the
717 * implementations capability.
718 * @throws NullPointerException if <code>duration</code> is <code>null</code>.
719 *
720 * @see #isShorterThan(Duration)
721 * @see #isLongerThan(Duration)
722 */
723 public int compare(Duration rhs) {
724
725 BigInteger maxintAsBigInteger = BigInteger.valueOf((long) Integer.MAX_VALUE);
726 BigInteger minintAsBigInteger = BigInteger.valueOf((long) Integer.MIN_VALUE);
727
728 // check for fields that are too large in this Duration
729 if (years != null && years.compareTo(maxintAsBigInteger) == 1) {
730 throw new UnsupportedOperationException(
731 DatatypeMessageFormatter.formatMessage(null, "TooLarge",
732 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.YEARS.toString(), years.toString()})
733 //this.getClass().getName() + "#compare(Duration duration)"
734 //+ " years too large to be supported by this implementation "
735 //+ years.toString()
736 );
737 }
738 if (months != null && months.compareTo(maxintAsBigInteger) == 1) {
739 throw new UnsupportedOperationException(
740 DatatypeMessageFormatter.formatMessage(null, "TooLarge",
741 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.MONTHS.toString(), months.toString()})
742
743 //this.getClass().getName() + "#compare(Duration duration)"
744 //+ " months too large to be supported by this implementation "
745 //+ months.toString()
746 );
761 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.HOURS.toString(), hours.toString()})
762
763 //this.getClass().getName() + "#compare(Duration duration)"
764 //+ " hours too large to be supported by this implementation "
765 //+ hours.toString()
766 );
767 }
768 if (minutes != null && minutes.compareTo(maxintAsBigInteger) == 1) {
769 throw new UnsupportedOperationException(
770 DatatypeMessageFormatter.formatMessage(null, "TooLarge",
771 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.MINUTES.toString(), minutes.toString()})
772
773 //this.getClass().getName() + "#compare(Duration duration)"
774 //+ " minutes too large to be supported by this implementation "
775 //+ minutes.toString()
776 );
777 }
778 if (seconds != null && seconds.toBigInteger().compareTo(maxintAsBigInteger) == 1) {
779 throw new UnsupportedOperationException(
780 DatatypeMessageFormatter.formatMessage(null, "TooLarge",
781 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.SECONDS.toString(), seconds.toString()})
782
783 //this.getClass().getName() + "#compare(Duration duration)"
784 //+ " seconds too large to be supported by this implementation "
785 //+ seconds.toString()
786 );
787 }
788
789 // check for fields that are too large in rhs Duration
790 BigInteger rhsYears = (BigInteger) rhs.getField(DatatypeConstants.YEARS);
791 if (rhsYears != null && rhsYears.compareTo(maxintAsBigInteger) == 1) {
792 throw new UnsupportedOperationException(
793 DatatypeMessageFormatter.formatMessage(null, "TooLarge",
794 new Object[]{this.getClass().getName() + "#compare(Duration duration)" + DatatypeConstants.YEARS.toString(), rhsYears.toString()})
795
796 //this.getClass().getName() + "#compare(Duration duration)"
797 //+ " years too large to be supported by this implementation "
798 //+ rhsYears.toString()
799 );
800 }
801 BigInteger rhsMonths = (BigInteger) rhs.getField(DatatypeConstants.MONTHS);
940
941 tempA.add(duration1);
942 tempB.add(duration2);
943 resultB = tempA.compare(tempB);
944 resultA = compareResults(resultA, resultB);
945 if (resultA == DatatypeConstants.INDETERMINATE) {
946 return DatatypeConstants.INDETERMINATE;
947 }
948
949 tempA = (XMLGregorianCalendar)TEST_POINTS[3].clone();
950 tempB = (XMLGregorianCalendar)TEST_POINTS[3].clone();
951
952 tempA.add(duration1);
953 tempB.add(duration2);
954 resultB = tempA.compare(tempB);
955 resultA = compareResults(resultA, resultB);
956
957 return resultA;
958 }
959
960 private int compareResults(int resultA, int resultB){
961
962 if ( resultB == DatatypeConstants.INDETERMINATE ) {
963 return DatatypeConstants.INDETERMINATE;
964 }
965 else if ( resultA!=resultB) {
966 return DatatypeConstants.INDETERMINATE;
967 }
968 return resultA;
969 }
970
971 /**
972 * Returns a hash code consistent with the definition of the equals method.
973 *
974 * @see Object#hashCode()
975 */
976 public int hashCode() {
977 // component wise hash is not correct because 1day = 24hours
978 Calendar cal = TEST_POINTS[0].toGregorianCalendar();
979 this.addTo(cal);
980 return (int) getCalendarTimeInMillis(cal);
990 * the {@link #DurationImpl(String)} constructor.
991 *
992 * <p>
993 * Formally, the following holds for any {@link Duration}
994 * object x.
995 * <pre>
996 * new Duration(x.toString()).equals(x)
997 * </pre>
998 *
999 * @return
1000 * Always return a non-null valid String object.
1001 */
1002 public String toString() {
1003 StringBuffer buf = new StringBuffer();
1004 if (signum < 0) {
1005 buf.append('-');
1006 }
1007 buf.append('P');
1008
1009 if (years != null) {
1010 buf.append(years + "Y");
1011 }
1012 if (months != null) {
1013 buf.append(months + "M");
1014 }
1015 if (days != null) {
1016 buf.append(days + "D");
1017 }
1018
1019 if (hours != null || minutes != null || seconds != null) {
1020 buf.append('T');
1021 if (hours != null) {
1022 buf.append(hours + "H");
1023 }
1024 if (minutes != null) {
1025 buf.append(minutes + "M");
1026 }
1027 if (seconds != null) {
1028 buf.append(toString(seconds) + "S");
1029 }
1030 }
1031
1032 return buf.toString();
1033 }
1034
1035 /**
1036 * <p>Turns {@link BigDecimal} to a string representation.</p>
1037 *
1038 * <p>Due to a behavior change in the {@link BigDecimal#toString()}
1039 * method in JDK1.5, this had to be implemented here.</p>
1040 *
1041 * @param bd <code>BigDecimal</code> to format as a <code>String</code>
1042 *
1043 * @return <code>String</code> representation of <code>BigDecimal</code>
1044 */
1045 private String toString(BigDecimal bd) {
1046 String intString = bd.unscaledValue().toString();
1047 int scale = bd.scale();
1048
1049 if (scale == 0) {
1050 return intString;
1051 }
1052
1053 /* Insert decimal point */
1054 StringBuffer buf;
1055 int insertionPoint = intString.length() - scale;
1056 if (insertionPoint == 0) { /* Point goes right before intVal */
1057 return "0." + intString;
1058 } else if (insertionPoint > 0) { /* Point goes inside intVal */
1059 buf = new StringBuffer(intString);
1060 buf.insert(insertionPoint, '.');
1061 } else { /* We must insert zeros between point and intVal */
1062 buf = new StringBuffer(3 - insertionPoint + intString.length());
1063 buf.append("0.");
1064 for (int i = 0; i < -insertionPoint; i++) {
1065 buf.append('0');
1066 }
1067 buf.append(intString);
1068 }
1069 return buf.toString();
1070 }
1071
1072 /**
1073 * Checks if a field is set.
1074 *
1075 * A field of a duration object may or may not be present.
1076 * This method can be used to test if a field is present.
1077 *
1078 * @param field
1079 * one of the six Field constants (YEARS,MONTHS,DAYS,HOURS,
1080 * MINUTES, or SECONDS.)
1081 * @return
1285 * will be discarded (for example, if the actual value is 2.5,
1286 * this method returns 2)
1287 */
1288 public int getSeconds() {
1289 return getInt(DatatypeConstants.SECONDS);
1290 }
1291
1292 /**
1293 * <p>Return the requested field value as an int.</p>
1294 *
1295 * <p>If field is not set, i.e. == null, 0 is returned.</p>
1296 *
1297 * @param field To get value for.
1298 *
1299 * @return int value of field or 0 if field is not set.
1300 */
1301 private int getInt(DatatypeConstants.Field field) {
1302 Number n = getField(field);
1303 if (n == null) {
1304 return 0;
1305 } else {
1306 return n.intValue();
1307 }
1308 }
1309
1310 /**
1311 * <p>Returns the length of the duration in milli-seconds.</p>
1312 *
1313 * <p>If the seconds field carries more digits than milli-second order,
1314 * those will be simply discarded (or in other words, rounded to zero.)
1315 * For example, for any Calendar value <code>x<code>,</p>
1316 * <pre>
1317 * <code>new Duration("PT10.00099S").getTimeInMills(x) == 10000</code>.
1318 * <code>new Duration("-PT10.00099S").getTimeInMills(x) == -10000</code>.
1319 * </pre>
1320 *
1321 * <p>
1322 * Note that this method uses the {@link #addTo(Calendar)} method,
1323 * which may work incorectly with {@link Duration} objects with
1324 * very large values in its fields. See the {@link #addTo(Calendar)}
1325 * method for details.
1326 *
1327 * @param startInstant
1328 * The length of a month/year varies. The <code>startInstant</code> is
1329 * used to disambiguate this variance. Specifically, this method
1330 * returns the difference between <code>startInstant</code> and
1331 * <code>startInstant+duration</code>
1332 *
1333 * @return milliseconds between <code>startInstant</code> and
1334 * <code>startInstant</code> plus this <code>Duration</code>
1335 *
1336 * @throws NullPointerException if <code>startInstant</code> parameter
1337 * is null.
1338 *
1339 */
1340 public long getTimeInMillis(final Calendar startInstant) {
1341 Calendar cal = (Calendar) startInstant.clone();
1342 addTo(cal);
1343 return getCalendarTimeInMillis(cal)
1344 - getCalendarTimeInMillis(startInstant);
1345 }
1346
1347 /**
1348 * <p>Returns the length of the duration in milli-seconds.</p>
1349 *
1350 * <p>If the seconds field carries more digits than milli-second order,
1351 * those will be simply discarded (or in other words, rounded to zero.)
1352 * For example, for any <code>Date</code> value <code>x<code>,</p>
1353 * <pre>
1354 * <code>new Duration("PT10.00099S").getTimeInMills(x) == 10000</code>.
1355 * <code>new Duration("-PT10.00099S").getTimeInMills(x) == -10000</code>.
1356 * </pre>
1357 *
1358 * <p>
1359 * Note that this method uses the {@link #addTo(Date)} method,
1360 * which may work incorectly with {@link Duration} objects with
1361 * very large values in its fields. See the {@link #addTo(Date)}
1362 * method for details.
1363 *
1364 * @param startInstant
1530 public Duration multiply(BigDecimal factor) {
1531 BigDecimal carry = ZERO;
1532 int factorSign = factor.signum();
1533 factor = factor.abs();
1534
1535 BigDecimal[] buf = new BigDecimal[6];
1536
1537 for (int i = 0; i < 5; i++) {
1538 BigDecimal bd = getFieldAsBigDecimal(FIELDS[i]);
1539 bd = bd.multiply(factor).add(carry);
1540
1541 buf[i] = bd.setScale(0, BigDecimal.ROUND_DOWN);
1542
1543 bd = bd.subtract(buf[i]);
1544 if (i == 1) {
1545 if (bd.signum() != 0) {
1546 throw new IllegalStateException(); // illegal carry-down
1547 } else {
1548 carry = ZERO;
1549 }
1550 } else {
1551 carry = bd.multiply(FACTORS[i]);
1552 }
1553 }
1554
1555 if (seconds != null) {
1556 buf[5] = seconds.multiply(factor).add(carry);
1557 } else {
1558 buf[5] = carry;
1559 }
1560
1561 return new DurationImpl(
1562 this.signum * factorSign >= 0,
1563 toBigInteger(buf[0], null == years),
1564 toBigInteger(buf[1], null == months),
1565 toBigInteger(buf[2], null == days),
1566 toBigInteger(buf[3], null == hours),
1567 toBigInteger(buf[4], null == minutes),
1568 (buf[5].signum() == 0 && seconds == null) ? null : buf[5]);
1569 }
1570
1571 /**
1572 * <p>Gets the value of the field as a {@link BigDecimal}.</p>
1573 *
1574 * <p>If the field is unset, return 0.</p>
1575 *
1576 * @param f Field to get value for.
1577 *
1578 * @return non-null valid {@link BigDecimal}.
1579 */
1580 private BigDecimal getFieldAsBigDecimal(DatatypeConstants.Field f) {
1581 if (f == DatatypeConstants.SECONDS) {
1582 if (seconds != null) {
1583 return seconds;
1584 } else {
1585 return ZERO;
1586 }
1587 } else {
1588 BigInteger bi = (BigInteger) getField(f);
1589 if (bi == null) {
1590 return ZERO;
1591 } else {
1592 return new BigDecimal(bi);
1593 }
1594 }
1595 }
1596
1597 /**
1598 * <p>BigInteger value of BigDecimal value.</p>
1599 *
1600 * @param value Value to convert.
1601 * @param canBeNull Can returned value be null?
1602 *
1603 * @return BigInteger value of BigDecimal, possibly null.
1604 */
1605 private static BigInteger toBigInteger(
1606 BigDecimal value,
1607 boolean canBeNull) {
1608 if (canBeNull && value.signum() == 0) {
1609 return null;
1610 } else {
1611 return value.unscaledValue();
1612 }
1613 }
1614
1615 /**
1616 * 1 unit of FIELDS[i] is equivalent to <code>FACTORS[i]</code> unit of
1617 * FIELDS[i+1].
1618 */
1619 private static final BigDecimal[] FACTORS = new BigDecimal[]{
1620 BigDecimal.valueOf(12),
1621 null/*undefined*/,
1622 BigDecimal.valueOf(24),
1623 BigDecimal.valueOf(60),
1624 BigDecimal.valueOf(60)
1625 };
1626
1627 /**
1628 * <p>Computes a new duration whose value is <code>this+rhs</code>.</p>
1629 *
1630 * <p>For example,</p>
1631 * <pre>
1632 * "1 day" + "-3 days" = "-2 days"
1633 * "1 year" + "1 day" = "1 year and 1 day"
1634 * "-(1 hour,50 minutes)" + "-20 minutes" = "-(1 hours,70 minutes)"
1635 * "15 hours" + "-3 days" = "-(2 days,9 hours)"
1636 * "1 year" + "-1 day" = IllegalStateException
1637 * </pre>
1638 *
1639 * <p>Since there's no way to meaningfully subtract 1 day from 1 month,
1947 * The updated time instant is then converted back into a
1948 * {@link Date} object and used to update the given {@link Date} object.
1949 *
1950 * <p>
1951 * This somewhat redundant computation is necessary to unambiguously
1952 * determine the duration of months and years.
1953 *
1954 * @param date
1955 * A date object whose value will be modified.
1956 * @throws NullPointerException
1957 * if the date parameter is null.
1958 */
1959 public void addTo(Date date) {
1960 Calendar cal = new GregorianCalendar();
1961 cal.setTime(date); // this will throw NPE if date==null
1962 this.addTo(cal);
1963 date.setTime(getCalendarTimeInMillis(cal));
1964 }
1965
1966 /**
1967 * <p>Stream Unique Identifier.</p>
1968 *
1969 * <p>TODO: Serialization should use the XML string representation as
1970 * the serialization format to ensure future compatibility.</p>
1971 */
1972 private static final long serialVersionUID = 1L;
1973
1974 /**
1975 * Writes {@link Duration} as a lexical representation
1976 * for maximum future compatibility.
1977 *
1978 * @return
1979 * An object that encapsulates the string
1980 * returned by <code>this.toString()</code>.
1981 */
1982 private Object writeReplace() throws IOException {
1983 return new DurationStream(this.toString());
1984 }
1985
1986 /**
1987 * Representation of {@link Duration} in the object stream.
1988 *
1989 * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
1990 */
1991 private static class DurationStream implements Serializable {
1992 private final String lexical;
1993
1994 private DurationStream(String _lexical) {
1995 this.lexical = _lexical;
1996 }
1997
1998 private Object readResolve() throws ObjectStreamException {
1999 // try {
2000 return new DurationImpl(lexical);
2001 // } catch( ParseException e ) {
2002 // throw new StreamCorruptedException("unable to parse "+lexical+" as duration");
2003 // }
2004 }
2005
2006 private static final long serialVersionUID = 1L;
2007 }
2008
2009 /**
2010 * Calls the {@link Calendar#getTimeInMillis} method.
2011 * Prior to JDK1.4, this method was protected and therefore
2012 * cannot be invoked directly.
2013 *
2014 * In future, this should be replaced by
2015 * <code>cal.getTimeInMillis()</code>
2016 */
2017 private static long getCalendarTimeInMillis(Calendar cal) {
2018 return cal.getTime().getTime();
2019 }
2020 }
|
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;
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 */
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
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
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]);
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 }
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 );
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);
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);
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
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
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,
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 }
|