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 21 package com.sun.org.apache.xerces.internal.jaxp.datatype; 22 23 import com.sun.org.apache.xerces.internal.util.DatatypeMessageFormatter; 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.math.RoundingMode; 30 import java.util.Calendar; 31 import java.util.Date; 32 import java.util.GregorianCalendar; 33 import java.util.TimeZone; 34 import javax.xml.datatype.DatatypeConstants; 35 import javax.xml.datatype.Duration; 36 import 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<B (A is shorter than B) 60 * <li>A>B (A is longer than B) 61 * <li>A==B (A and B are of the same duration) 62 * <li>A<>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 */ 103 class 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 }