001package com.hfg.units; 002 003 004import java.text.DecimalFormat; 005import java.util.ArrayList; 006import java.util.HashMap; 007import java.util.List; 008import java.util.Map; 009import java.util.regex.Matcher; 010import java.util.regex.Pattern; 011 012import com.hfg.exception.InvalidValueException; 013import com.hfg.util.CompareUtil; 014import com.hfg.util.StringBuilderPlus; 015import com.hfg.util.StringUtil; 016import com.hfg.util.collection.CollectionUtil; 017 018//------------------------------------------------------------------------------ 019/** 020 Quantifiable amount. Amount plus units. 021 <div> 022 @author J. Alex Taylor, hairyfatguy.com 023 </div> 024 */ 025//------------------------------------------------------------------------------ 026// com.hfg XML/HTML Coding Library 027// 028// This library is free software; you can redistribute it and/or 029// modify it under the terms of the GNU Lesser General Public 030// License as published by the Free Software Foundation; either 031// version 2.1 of the License, or (at your option) any later version. 032// 033// This library is distributed in the hope that it will be useful, 034// but WITHOUT ANY WARRANTY; without even the implied warranty of 035// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 036// Lesser General Public License for more details. 037// 038// You should have received a copy of the GNU Lesser General Public 039// License along with this library; if not, write to the Free Software 040// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 041// 042// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 043// jataylor@hairyfatguy.com 044//------------------------------------------------------------------------------ 045 046public class Quantity implements Comparable 047{ 048 private Double mDoubleValue; 049 private Integer mIntValue; 050 private Long mLongValue; 051 private Unit mUnit; 052 private Map<QuantityType, SI_ScalingFactor> mScalingFactorMap; 053 private List<Quantity> mSubQuantities; 054 055 private static final Pattern sIntRegex = Pattern.compile("([\\-+]?\\d+)\\s?+([^\\d\\.]+\\.?)"); 056 private static final Pattern sScientificRegex = Pattern.compile("([\\-+]?(?:\\d+)?(?:\\.\\d+)?(?:E\\-?\\d+)?)\\s?+(.+)"); 057 058 //########################################################################### 059 // CONSTRUCTORS 060 //########################################################################### 061 062 //--------------------------------------------------------------------------- 063 /** 064 Convenience constructor that will call Unit.valueOf() on the specified unit string. 065 * @param inValue the numeric amount as a double 066 * @param inUnit the volume unit of the specified amount 067 */ 068 public Quantity(Double inValue, String inUnit) 069 { 070 this(inValue, Unit.valueOf(inUnit)); 071 } 072 073 //--------------------------------------------------------------------------- 074 /** 075 Convenience constructor that will call Unit.valueOf() on the specified unit string. 076 * @param inValue the numeric amount as a float 077 * @param inUnit the volume unit of the specified amount 078 */ 079 public Quantity(Float inValue, String inUnit) 080 { 081 this(inValue, Unit.valueOf(inUnit)); 082 } 083 084 //--------------------------------------------------------------------------- 085 /** 086 Convenience constructor that will call Unit.valueOf() on the specified unit string. 087 * @param inValue the numeric amount as an integer 088 * @param inUnit the volume unit of the specified amount 089 */ 090 public Quantity(Integer inValue, String inUnit) 091 { 092 this(inValue, Unit.valueOf(inUnit)); 093 } 094 095 //--------------------------------------------------------------------------- 096 /** 097 Convenience constructor that will call Unit.valueOf() on the specified unit string. 098 * @param inValue the numeric amount as an integer 099 * @param inUnit the volume unit of the specified amount 100 */ 101 public Quantity(Long inValue, String inUnit) 102 { 103 this(inValue, Unit.valueOf(inUnit)); 104 } 105 106 //--------------------------------------------------------------------------- 107 /** 108 Convenience constructor. 109 * @param inValue the string value 110 */ 111 public Quantity(String inValue) 112 { 113 if (inValue != null) 114 { 115 String trimmedValue = inValue.trim(); 116 int length = trimmedValue.length(); 117 118 // To allow for compound quantities we'll chew off a piece at a time 119 int start = 0; 120 while (start < length) 121 { 122 Matcher m = sIntRegex.matcher(trimmedValue); 123 if (m.find(start) 124 && m.start() == start) 125 { 126 if (start > 0 127 || m.end() < length) 128 { 129 String unitString = m.group(2).trim(); 130 if (unitString.endsWith(" and")) 131 { 132 unitString = unitString.substring(0, unitString.length() - 4); 133 } 134 135 if (unitString.endsWith(",") || unitString.endsWith(".")) 136 { 137 unitString = unitString.substring(0, unitString.length() - 1); 138 } 139 140 addSubQuantity(Integer.parseInt(m.group(1)), Unit.valueOf(unitString)); 141 } 142 else 143 { 144 mIntValue = Integer.parseInt(m.group(1)); 145 mUnit = Unit.valueOf(m.group(2)); 146 } 147 148 start = m.end(); 149 } 150 else 151 { 152 m = sScientificRegex.matcher(trimmedValue); 153 if (m.find(start) 154 && m.start() == start) 155 { 156 if (start > 0 157 || m.end() < length) 158 { 159 String unitString = m.group(2); 160 if (unitString.endsWith(",")) 161 { 162 unitString = unitString.substring(0, unitString.length() - 1); 163 } 164 165 addSubQuantity(Double.parseDouble(m.group(1)), Unit.valueOf(unitString)); 166 } 167 else 168 { 169 mDoubleValue = Double.parseDouble(m.group(1)); 170 mUnit = Unit.valueOf(m.group(2)); 171 } 172 173 start = m.end(); 174 } 175 else 176 { 177 throw new UnitException("Couldn't parse " + StringUtil.singleQuote(inValue) + " into a Quantity!"); 178 } 179 } 180 } 181 } 182 } 183 184 //--------------------------------------------------------------------------- 185 public Quantity(Double inValue, Unit inUnit) 186 { 187 nullCheckAmount(inValue); 188 mDoubleValue = inValue; 189 mUnit = inUnit; 190 } 191 192 //--------------------------------------------------------------------------- 193 public Quantity(Float inValue, Unit inUnit) 194 { 195 nullCheckAmount(inValue); 196 mDoubleValue = (inValue != null ? inValue.doubleValue() : null); 197 mUnit = inUnit; 198 } 199 200 //--------------------------------------------------------------------------- 201 public Quantity(Integer inValue, Unit inUnit) 202 { 203 nullCheckAmount(inValue); 204 mIntValue = inValue; 205 mUnit = inUnit; 206 } 207 208 //--------------------------------------------------------------------------- 209 public Quantity(Long inValue, Unit inUnit) 210 { 211 nullCheckAmount(inValue); 212 mLongValue = inValue; 213 mUnit = inUnit; 214 } 215 216 //--------------------------------------------------------------------------- 217 private Quantity(List<Quantity> inSubQuantities) 218 { 219 mSubQuantities = inSubQuantities; 220 } 221 222 //--------------------------------------------------------------------------- 223 private void nullCheckAmount(Number inValue) 224 { 225 if (null == inValue) 226 { 227 throw new InvalidValueException("A non-null amount must be specified!"); 228 } 229 } 230 231 //########################################################################### 232 // PUBLIC METHODS 233 //########################################################################### 234 235 //--------------------------------------------------------------------------- 236 @Override 237 public String toString() 238 { 239 return toString((String)null); 240 } 241 242 //--------------------------------------------------------------------------- 243 /** 244 * Outputs the Quantity as a numeric value followed by the units where the 245 * numeric portion is formatted according to the specified sprintf string. 246 * @param inFmtString a sprintf format specification 247 * @return the formatted Quantity value 248 */ 249 public String toString(String inFmtString) 250 { 251 StringBuilderPlus buffer = new StringBuilderPlus().setDelimiter(" "); 252 253 if (mSubQuantities != null) 254 { 255 for (Quantity subQuantity : mSubQuantities) 256 { 257 buffer.delimitedAppend(subQuantity.toString(inFmtString)); 258 } 259 } 260 else 261 { 262 Double doubleValue = doubleValue(); 263 if (doubleValue != null) 264 { 265 if (StringUtil.isSet(inFmtString)) 266 { 267 // Apply the specified formatting 268 buffer.append(String.format(inFmtString, doubleValue)); 269 } 270 else if (doubleValue == Math.floor(doubleValue) 271 && !Double.isInfinite(doubleValue)) 272 { 273 // No formatting specified but the value can be represented by an integer 274 buffer.append(doubleValue.intValue()); 275 } 276 else 277 { 278 buffer.append(doubleValue + ""); 279 } 280 281 boolean isPlural = (doubleValue != 1.0); 282 buffer.delimitedAppend(mUnit.computeUnitLabel(mScalingFactorMap, isPlural)); 283 } 284 } 285 286 return buffer.toString(); 287 } 288 289 //--------------------------------------------------------------------------- 290 /** 291 * Outputs the Quantity as a numeric value followed by the units where the 292 * numeric portion is formatted according to the specified DecimalFormat object. 293 * @param inFormat a DecimalFormat format specification 294 * @return the formatted Quantity value 295 */ 296 public String toString(DecimalFormat inFormat) 297 { 298 StringBuilderPlus buffer = new StringBuilderPlus().setDelimiter(" "); 299 300 if (mSubQuantities != null) 301 { 302 for (Quantity subQuantity : mSubQuantities) 303 { 304 buffer.delimitedAppend(subQuantity.toString(inFormat)); 305 } 306 } 307 else 308 { 309 Double doubleValue = doubleValue(); 310 if (doubleValue != null) 311 { 312 if (inFormat != null) 313 { 314 buffer.append(inFormat.format(doubleValue)); 315 } 316 else if (doubleValue == Math.floor(doubleValue) 317 && !Double.isInfinite(doubleValue)) 318 { 319 // No formatting specified but the value can be represented by an integer 320 buffer.append(doubleValue.intValue()); 321 } 322 else 323 { 324 buffer.append(doubleValue + ""); 325 } 326 327 boolean isPlural = (doubleValue != 1.0); 328 buffer.delimitedAppend(mUnit.computeUnitLabel(mScalingFactorMap, isPlural)); 329 } 330 } 331 332 return buffer.toString(); 333 } 334 335 //--------------------------------------------------------------------------- 336 @Override 337 public boolean equals(Object inObj2) 338 { 339 return (inObj2 != null 340 && inObj2 instanceof Quantity 341 && (this == inObj2 342 || 0 == compareTo(inObj2))); 343 } 344 345 //--------------------------------------------------------------------------- 346 @Override 347 public int hashCode() 348 { 349 int hashCode = 0; 350 351 Double doubleValue = doubleValue(); 352 if (doubleValue != null) 353 { 354 hashCode += doubleValue.hashCode(); 355 } 356 357 if (getUnit() != null) 358 { 359 hashCode += 31 * getUnit().hashCode(); 360 } 361 362 if (CollectionUtil.hasValues(mScalingFactorMap)) 363 { 364 for (QuantityType quantityType : mScalingFactorMap.keySet()) 365 { 366 hashCode += 31 * quantityType.hashCode() + mScalingFactorMap.get(quantityType).hashCode(); 367 } 368 } 369 370 return hashCode; 371 } 372 373 //--------------------------------------------------------------------------- 374 @Override 375 public int compareTo(Object inObj2) 376 { 377 int result = -1; 378 if (inObj2 != null) 379 { 380 if (inObj2 instanceof Quantity) 381 { 382 Quantity quantity2 = (Quantity) inObj2; 383 384 result = CompareUtil.compare(getUnit(), quantity2.getUnit()); 385 386 if (0 == result) 387 { 388 result = CompareUtil.compare(doubleValue(), quantity2.doubleValue()); 389 } 390 391 if (0 == result) 392 { 393 result = CompareUtil.compare(mScalingFactorMap, quantity2.mScalingFactorMap); 394 } 395 } 396 } 397 398 return result; 399 } 400 401 //--------------------------------------------------------------------------- 402 public void scale(QuantityType inQuantityType, SI_ScalingFactor inScalingFactor) 403 { 404 if (null == mScalingFactorMap) 405 { 406 mScalingFactorMap = new HashMap<>(4); 407 } 408 409 mScalingFactorMap.put(inQuantityType, inScalingFactor); 410 } 411 412 //--------------------------------------------------------------------------- 413 public Quantity convertTo(Unit inUnit) 414 { 415 Quantity convertedQuantity = null; 416 417 if (mSubQuantities != null) 418 { 419 // It's a compound quantity. Convert sub-quantities to the unit of the last 420 // sub-quantity and total them. 421 for (Quantity subquantity : mSubQuantities) 422 { 423 if (null == convertedQuantity) 424 { 425 convertedQuantity = subquantity.convertTo(inUnit); 426 } 427 else 428 { 429 convertedQuantity = convertedQuantity.add(subquantity.convertTo(inUnit)); 430 } 431 } 432 } 433 else 434 { 435 // It's a regular quantity 436 if (getUnit().equals(inUnit)) 437 { 438 // It's already in the desired units 439 convertedQuantity = this; 440 } 441 else 442 { 443 // Our standard of interchange is the base SI unit 444 Double doubleValue = mUnit.computeBaseSIValue(doubleValue()); 445 446 convertedQuantity = new Quantity(inUnit.computeValueFromBaseSIValue(doubleValue), inUnit); 447 } 448 } 449 450 return convertedQuantity; 451 } 452 453 //--------------------------------------------------------------------------- 454 public Quantity invert() 455 { 456 return new Quantity( - doubleValue(), getUnit()); 457 } 458 459 //--------------------------------------------------------------------------- 460 public Quantity multiplyBy(double inValue) 461 { 462 Quantity newQuantity; 463 if (mSubQuantities != null) 464 { 465 List<Quantity> newSubQuantities = new ArrayList<>(2); 466 for (Quantity subQuantity : mSubQuantities) 467 { 468 newSubQuantities.add(subQuantity.multiplyBy(inValue)); 469 } 470 471 newQuantity = new Quantity(newSubQuantities); 472 } 473 else 474 { 475 newQuantity = new Quantity(doubleValue() * inValue, getUnit()); 476 } 477 478 479 return newQuantity; 480 } 481 482 //--------------------------------------------------------------------------- 483 public Quantity divideBy(double inValue) 484 { 485 Quantity newQuantity; 486 if (mSubQuantities != null) 487 { 488 List<Quantity> newSubQuantities = new ArrayList<>(2); 489 for (Quantity subQuantity : mSubQuantities) 490 { 491 newSubQuantities.add(subQuantity.divideBy(inValue)); 492 } 493 494 newQuantity = new Quantity(newSubQuantities); 495 } 496 else 497 { 498 newQuantity = new Quantity(doubleValue() / inValue, getUnit()); 499 } 500 501 return newQuantity; 502 } 503 504 //--------------------------------------------------------------------------- 505 public Quantity add(Quantity inValue) 506 { 507 testQuantityTypeEquivalence(inValue); 508 509 Quantity result; 510 if (inValue != null) 511 { 512 Quantity convertedAdditionalValue = inValue.convertTo(getUnit()); 513 514 result = new Quantity(doubleValue() + convertedAdditionalValue.doubleValue(), getUnit()); 515 } 516 else 517 { 518 result = this; 519 } 520 521 return result; 522 } 523 524 //--------------------------------------------------------------------------- 525 public Quantity subtract(Quantity inValue) 526 { 527 testQuantityTypeEquivalence(inValue); 528 529 Quantity result; 530 if (inValue != null) 531 { 532 Quantity convertedAdditionalValue = inValue.convertTo(getUnit()); 533 534 result = new Quantity(doubleValue() - convertedAdditionalValue.doubleValue(), getUnit()); 535 } 536 else 537 { 538 result = this; 539 } 540 541 return result; 542 } 543 544 //--------------------------------------------------------------------------- 545 public boolean lessThan(Quantity inValue) 546 { 547 testQuantityTypeEquivalence(inValue); 548 549 boolean result = false; 550 if (inValue != null) 551 { 552 Quantity convertedValue = inValue.convertTo(getUnit()); 553 554 result = doubleValue() < convertedValue.doubleValue(); 555 } 556 557 return result; 558 } 559 560 //--------------------------------------------------------------------------- 561 public boolean lessThanOrEqualTo(Quantity inValue) 562 { 563 testQuantityTypeEquivalence(inValue); 564 565 boolean result = false; 566 if (inValue != null) 567 { 568 Quantity convertedValue = inValue.convertTo(getUnit()); 569 570 result = doubleValue() <= convertedValue.doubleValue(); 571 } 572 573 return result; 574 } 575 576 //--------------------------------------------------------------------------- 577 public boolean greaterThan(Quantity inValue) 578 { 579 testQuantityTypeEquivalence(inValue); 580 581 boolean result = false; 582 if (inValue != null) 583 { 584 Quantity convertedValue = inValue.convertTo(getUnit()); 585 586 result = doubleValue() > convertedValue.doubleValue(); 587 } 588 589 return result; 590 } 591 592 //--------------------------------------------------------------------------- 593 public boolean greaterThanOrEqualTo(Quantity inValue) 594 { 595 testQuantityTypeEquivalence(inValue); 596 597 boolean result = false; 598 if (inValue != null) 599 { 600 Quantity convertedValue = inValue.convertTo(getUnit()); 601 602 result = doubleValue() >= convertedValue.doubleValue(); 603 } 604 605 return result; 606 } 607 608 //--------------------------------------------------------------------------- 609 public Unit getUnit() 610 { 611 if (null == mUnit 612 && mSubQuantities != null) 613 { 614 mUnit = mSubQuantities.get(mSubQuantities.size() - 1).getUnit(); 615 } 616 617 return mUnit; 618 } 619 620 //--------------------------------------------------------------------------- 621 public Double doubleValue() 622 { 623 Double value; 624 625 if (mSubQuantities != null) 626 { 627 // It's a compound quantity. Convert sub-quantities to the unit of the last 628 // sub-quantity and total them. 629 Unit targetUnit = mSubQuantities.get(mSubQuantities.size() - 1).getUnit(); 630 631 double totalDoubleValue = 0; 632 for (Quantity subquantity : mSubQuantities) 633 { 634 totalDoubleValue += subquantity.convertTo(targetUnit).doubleValue(); 635 } 636 637 value = totalDoubleValue; 638 } 639 else 640 { 641 // It's a regular quantity 642 value = mDoubleValue; 643 if (null == value) 644 { 645 if (mIntValue != null) 646 { 647 value = mIntValue.doubleValue(); 648 } 649 else if (mLongValue != null) 650 { 651 value = mLongValue.doubleValue(); 652 } 653 } 654 655 // Scale the value? 656 if (value != null 657 && CollectionUtil.hasValues(mScalingFactorMap)) 658 { 659 value = mUnit.computeScaledValue(value, mScalingFactorMap); 660 } 661 } 662 663 return value; 664 } 665 666 //--------------------------------------------------------------------------- 667 public Float floatValue() 668 { 669 Double doubleValue = doubleValue(); 670 671 return (doubleValue != null ? doubleValue.floatValue() : null); 672 } 673 674 //--------------------------------------------------------------------------- 675 public Integer intValue() 676 { 677 Integer value; 678 679 if (mSubQuantities != null) 680 { 681 // It's a compound quantity. Convert sub-quantities to the unit of the last 682 // sub-quantity and total them. 683 Unit targetUnit = mSubQuantities.get(mSubQuantities.size() - 1).getUnit(); 684 685 int totalIntValue = 0; 686 for (Quantity subquantity : mSubQuantities) 687 { 688 totalIntValue += subquantity.convertTo(targetUnit).intValue(); 689 } 690 691 value = totalIntValue; 692 } 693 else 694 { 695 // It's a regular quantity 696 value = mIntValue; 697 if (null == value) 698 { 699 if (mDoubleValue != null) 700 { 701 value = mDoubleValue.intValue(); 702 } 703 else if (mLongValue != null) 704 { 705 value = mLongValue.intValue(); 706 } 707 } 708 709 // Scale the value? 710 if (value != null 711 && CollectionUtil.hasValues(mScalingFactorMap)) 712 { 713 value = (int) mUnit.computeScaledValue(value, mScalingFactorMap); 714 } 715 } 716 717 return value; 718 } 719 720 //--------------------------------------------------------------------------- 721 public Long longValue() 722 { 723 Long value; 724 725 if (mSubQuantities != null) 726 { 727 // It's a compound quantity. Convert sub-quantities to the unit of the last 728 // sub-quantity and total them. 729 Unit targetUnit = mSubQuantities.get(mSubQuantities.size() - 1).getUnit(); 730 731 long totalLongValue = 0; 732 for (Quantity subquantity : mSubQuantities) 733 { 734 totalLongValue += subquantity.convertTo(targetUnit).longValue(); 735 } 736 737 value = totalLongValue; 738 } 739 else 740 { 741 // It's a regular quantity 742 value = mLongValue; 743 if (null == value) 744 { 745 if (mDoubleValue != null) 746 { 747 value = mDoubleValue.longValue(); 748 } 749 else if (mIntValue != null) 750 { 751 value = mIntValue.longValue(); 752 } 753 } 754 755 // Scale the value? 756 if (value != null 757 && CollectionUtil.hasValues(mScalingFactorMap)) 758 { 759 value = (long) mUnit.computeScaledValue(value, mScalingFactorMap); 760 } 761 } 762 763 return value; 764 } 765 766 //--------------------------------------------------------------------------- 767 public Quantity autoScale() 768 { 769 return autoScale(SI_ScalingFactor.values()); 770 } 771 772 //--------------------------------------------------------------------------- 773 public Quantity autoScale(SI_ScalingFactor[] inScalingValues) 774 { 775 Quantity scaledQuantity = null; 776 777 double doubleValue = doubleValue(); 778 if (doubleValue > 900 779 || doubleValue < 0.1) 780 { 781 for (int i = 1; i < inScalingValues.length; i++) 782 { 783 if (doubleValue < inScalingValues[i - 1].getScalingFactor() 784 && doubleValue >= inScalingValues[i].getScalingFactor()) 785 { 786 if (StringUtil.isSet(getUnit().getAbbrev()) 787 || ! getUnit().hasSubUnits()) 788 { 789 scaledQuantity = this.convertTo(new Unit(getUnit(), inScalingValues[i])); 790 } 791 else 792 { 793 List<SubUnit> subUnits = new ArrayList<>(getUnit().getSubUnits()); 794 subUnits.set(0, new SubUnit(subUnits.get(0).getUnit(), inScalingValues[i], subUnits.get(0).getPow())); 795 scaledQuantity = this.convertTo(new Unit(subUnits)); 796 } 797 798 break; 799 } 800 } 801 } 802 803 return (scaledQuantity != null ? scaledQuantity : this); 804 } 805 806 //--------------------------------------------------------------------------- 807 /** 808 Performs a validation test to ensure that the unit is of the expected quantity type. 809 @param inExpectedQuantityType the quantity type that is expected. 810 @throws InvalidValueException if the unit isn't set or is not of the expected type. 811 */ 812 public void verifyQuantityType(QuantityType inExpectedQuantityType) 813 { 814 if (null == getUnit() 815 || ! getUnit().getQuantityType().equals(inExpectedQuantityType)) 816 { 817 throw new InvalidValueException("The quantity " + StringUtil.singleQuote(toString()) + " isn't specified in " + inExpectedQuantityType + " units!"); 818 } 819 } 820 821 //########################################################################### 822 // PRIVATE METHODS 823 //########################################################################### 824 825 //--------------------------------------------------------------------------- 826 private void addSubQuantity(Integer inValue, Unit inUnit) 827 { 828 if (null == mSubQuantities) 829 { 830 mSubQuantities = new ArrayList<>(2); 831 } 832 833 mSubQuantities.add(new Quantity(inValue, inUnit)); 834 } 835 836 //--------------------------------------------------------------------------- 837 private void addSubQuantity(Double inValue, Unit inUnit) 838 { 839 if (null == mSubQuantities) 840 { 841 mSubQuantities = new ArrayList<>(2); 842 } 843 844 mSubQuantities.add(new Quantity(inValue, inUnit)); 845 } 846 847 //--------------------------------------------------------------------------- 848 private void testQuantityTypeEquivalence(Quantity inValue) 849 { 850 Unit unit1 = getUnit(); 851 if (null == unit1 852 && mSubQuantities != null) 853 { 854 unit1 = mSubQuantities.get(0).getUnit(); 855 } 856 857 Unit unit2 = inValue.getUnit(); 858 if (null == unit2 859 && inValue.mSubQuantities != null) 860 { 861 unit2 = inValue.mSubQuantities.get(0).getUnit(); 862 } 863 864 if (! unit1.getQuantityType().equals(unit2.getQuantityType())) 865 { 866 throw new UnitException("Quantity Type mismatch (" + unit1.getQuantityType() + " vs. " + unit2.getQuantityType() + ")!"); 867 } 868 } 869 870}