1 // © 2020 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 package com.ibm.icu.impl.units; 4 5 import java.math.BigDecimal; 6 import java.util.ArrayList; 7 import java.util.Collections; 8 import java.util.Comparator; 9 10 import com.ibm.icu.util.BytesTrie; 11 import com.ibm.icu.util.CharsTrie; 12 import com.ibm.icu.util.CharsTrieBuilder; 13 import com.ibm.icu.util.ICUCloneNotSupportedException; 14 import com.ibm.icu.util.MeasureUnit; 15 import com.ibm.icu.util.StringTrieBuilder; 16 17 public class MeasureUnitImpl { 18 19 /** 20 * The full unit identifier. Null if not computed. 21 */ 22 private String identifier = null; 23 /** 24 * The complexity, either SINGLE, COMPOUND, or MIXED. 25 */ 26 private MeasureUnit.Complexity complexity = MeasureUnit.Complexity.SINGLE; 27 /** 28 * The list of single units. These may be summed or multiplied, based on the 29 * value of the complexity field. 30 * <p> 31 * The "dimensionless" unit (SingleUnitImpl default constructor) must not be added to this list. 32 * <p> 33 * The "dimensionless" <code>MeasureUnitImpl</code> has an empty <code>singleUnits</code>. 34 */ 35 private final ArrayList<SingleUnitImpl> singleUnits; 36 MeasureUnitImpl()37 public MeasureUnitImpl() { 38 singleUnits = new ArrayList<>(); 39 } 40 MeasureUnitImpl(SingleUnitImpl singleUnit)41 public MeasureUnitImpl(SingleUnitImpl singleUnit) { 42 this(); 43 this.appendSingleUnit(singleUnit); 44 } 45 46 /** 47 * Parse a unit identifier into a MeasureUnitImpl. 48 * 49 * @param identifier The unit identifier string. 50 * @return A newly parsed object. 51 * @throws IllegalArgumentException in case of incorrect/non-parsed identifier. 52 */ forIdentifier(String identifier)53 public static MeasureUnitImpl forIdentifier(String identifier) { 54 return UnitsParser.parseForIdentifier(identifier); 55 } 56 57 /** 58 * Used for currency units. 59 */ forCurrencyCode(String currencyCode)60 public static MeasureUnitImpl forCurrencyCode(String currencyCode) { 61 MeasureUnitImpl result = new MeasureUnitImpl(); 62 result.identifier = currencyCode; 63 return result; 64 } 65 copy()66 public MeasureUnitImpl copy() { 67 MeasureUnitImpl result = new MeasureUnitImpl(); 68 result.complexity = this.complexity; 69 result.identifier = this.identifier; 70 for (SingleUnitImpl single : this.singleUnits) { 71 result.singleUnits.add(single.copy()); 72 } 73 return result; 74 } 75 76 /** 77 * Returns the list of simple units. 78 */ getSingleUnits()79 public ArrayList<SingleUnitImpl> getSingleUnits() { 80 return singleUnits; 81 } 82 83 /** 84 * Mutates this MeasureUnitImpl to take the reciprocal. 85 */ takeReciprocal()86 public void takeReciprocal() { 87 this.identifier = null; 88 for (SingleUnitImpl singleUnit : 89 this.singleUnits) { 90 singleUnit.setDimensionality(singleUnit.getDimensionality() * -1); 91 } 92 } 93 extractIndividualUnitsWithIndices()94 public ArrayList<MeasureUnitImplWithIndex> extractIndividualUnitsWithIndices() { 95 ArrayList<MeasureUnitImplWithIndex> result = new ArrayList<>(); 96 if (this.getComplexity() == MeasureUnit.Complexity.MIXED) { 97 // In case of mixed units, each single unit can be considered as a stand alone MeasureUnitImpl. 98 int i = 0; 99 for (SingleUnitImpl singleUnit : 100 this.getSingleUnits()) { 101 result.add(new MeasureUnitImplWithIndex(i++, new MeasureUnitImpl(singleUnit))); 102 } 103 104 return result; 105 } 106 107 result.add(new MeasureUnitImplWithIndex(0, this.copy())); 108 return result; 109 } 110 111 /** 112 * Applies dimensionality to all the internal single units. 113 * For example: <b>square-meter-per-second</b>, when we apply dimensionality -2, it will be <b>square-second-per-p4-meter</b> 114 */ applyDimensionality(int dimensionality)115 public void applyDimensionality(int dimensionality) { 116 for (SingleUnitImpl singleUnit : 117 singleUnits) { 118 singleUnit.setDimensionality(singleUnit.getDimensionality() * dimensionality); 119 } 120 } 121 122 /** 123 * Mutates this MeasureUnitImpl to append a single unit. 124 * 125 * @return true if a new item was added. If unit is the dimensionless unit, 126 * it is never added: the return value will always be false. 127 */ appendSingleUnit(SingleUnitImpl singleUnit)128 public boolean appendSingleUnit(SingleUnitImpl singleUnit) { 129 identifier = null; 130 131 if (singleUnit == null) { 132 // Do not append dimensionless units. 133 return false; 134 } 135 136 // Find a similar unit that already exists, to attempt to coalesce 137 SingleUnitImpl oldUnit = null; 138 for (SingleUnitImpl candidate : this.singleUnits) { 139 if (candidate.isCompatibleWith(singleUnit)) { 140 oldUnit = candidate; 141 break; 142 } 143 } 144 145 if (oldUnit != null) { 146 // Both dimensionalities will be positive, or both will be negative, by 147 // virtue of isCompatibleWith(). 148 oldUnit.setDimensionality(oldUnit.getDimensionality() + singleUnit.getDimensionality()); 149 150 return false; 151 } 152 153 // Add a copy of singleUnit 154 this.singleUnits.add(singleUnit.copy()); 155 156 // If the MeasureUnitImpl is `UMEASURE_UNIT_SINGLE` and after the appending a unit, the singleUnits contains 157 // more than one. thus means the complexity should be `UMEASURE_UNIT_COMPOUND` 158 if (this.singleUnits.size() > 1 && this.complexity == MeasureUnit.Complexity.SINGLE) { 159 this.setComplexity(MeasureUnit.Complexity.COMPOUND); 160 } 161 162 return true; 163 } 164 165 /** 166 * Transform this MeasureUnitImpl into a MeasureUnit, simplifying if possible. 167 * <p> 168 * NOTE: this function must be called from a thread-safe class 169 */ build()170 public MeasureUnit build() { 171 return MeasureUnit.fromMeasureUnitImpl(this); 172 } 173 174 /** 175 * @return SingleUnitImpl 176 * @throws UnsupportedOperationException if the object could not be converted to SingleUnitImpl. 177 */ getSingleUnitImpl()178 public SingleUnitImpl getSingleUnitImpl() { 179 if (this.singleUnits.size() == 0) { 180 return new SingleUnitImpl(); 181 } 182 if (this.singleUnits.size() == 1) { 183 return this.singleUnits.get(0).copy(); 184 } 185 186 throw new UnsupportedOperationException(); 187 } 188 189 /** 190 * Returns the CLDR unit identifier and null if not computed. 191 */ getIdentifier()192 public String getIdentifier() { 193 return identifier; 194 } 195 getComplexity()196 public MeasureUnit.Complexity getComplexity() { 197 return complexity; 198 } 199 setComplexity(MeasureUnit.Complexity complexity)200 public void setComplexity(MeasureUnit.Complexity complexity) { 201 this.complexity = complexity; 202 } 203 204 /** 205 * Normalizes the MeasureUnitImpl and generates the identifier string in place. 206 */ serialize()207 public void serialize() { 208 if (this.getSingleUnits().size() == 0) { 209 // Dimensionless, constructed by the default constructor: no appending 210 // to this.result, we wish it to contain the zero-length string. 211 return; 212 } 213 214 215 if (this.complexity == MeasureUnit.Complexity.COMPOUND) { 216 // Note: don't sort a MIXED unit 217 Collections.sort(this.getSingleUnits(), new SingleUnitComparator()); 218 } 219 220 StringBuilder result = new StringBuilder(); 221 boolean beforePer = true; 222 boolean firstTimeNegativeDimension = false; 223 for (SingleUnitImpl singleUnit : 224 this.getSingleUnits()) { 225 if (beforePer && singleUnit.getDimensionality() < 0) { 226 beforePer = false; 227 firstTimeNegativeDimension = true; 228 } else if (singleUnit.getDimensionality() < 0) { 229 firstTimeNegativeDimension = false; 230 } 231 232 if (this.getComplexity() == MeasureUnit.Complexity.MIXED) { 233 if (result.length() != 0) { 234 result.append("-and-"); 235 } 236 } else { 237 if (firstTimeNegativeDimension) { 238 if (result.length() == 0) { 239 result.append("per-"); 240 } else { 241 result.append("-per-"); 242 } 243 } else { 244 if (result.length() != 0) { 245 result.append("-"); 246 } 247 } 248 } 249 250 result.append(singleUnit.getNeutralIdentifier()); 251 } 252 253 this.identifier = result.toString(); 254 } 255 256 @Override toString()257 public String toString() { 258 return "MeasureUnitImpl [" + build().getIdentifier() + "]"; 259 } 260 261 public enum CompoundPart { 262 // Represents "-per-" 263 PER(0), 264 // Represents "-" 265 TIMES(1), 266 // Represents "-and-" 267 AND(2); 268 269 private final int index; 270 CompoundPart(int index)271 CompoundPart(int index) { 272 this.index = index; 273 } 274 getCompoundPartFromTrieIndex(int trieIndex)275 public static CompoundPart getCompoundPartFromTrieIndex(int trieIndex) { 276 int index = trieIndex - UnitsData.Constants.kCompoundPartOffset; 277 switch (index) { 278 case 0: 279 return CompoundPart.PER; 280 case 1: 281 return CompoundPart.TIMES; 282 case 2: 283 return CompoundPart.AND; 284 default: 285 throw new AssertionError("CompoundPart index must be 0, 1 or 2"); 286 } 287 } 288 getTrieIndex()289 public int getTrieIndex() { 290 return this.index + UnitsData.Constants.kCompoundPartOffset; 291 } 292 getValue()293 public int getValue() { 294 return index; 295 } 296 } 297 298 public enum PowerPart { 299 P2(2), 300 P3(3), 301 P4(4), 302 P5(5), 303 P6(6), 304 P7(7), 305 P8(8), 306 P9(9), 307 P10(10), 308 P11(11), 309 P12(12), 310 P13(13), 311 P14(14), 312 P15(15); 313 314 private final int power; 315 PowerPart(int power)316 PowerPart(int power) { 317 this.power = power; 318 } 319 getPowerFromTrieIndex(int trieIndex)320 public static int getPowerFromTrieIndex(int trieIndex) { 321 return trieIndex - UnitsData.Constants.kPowerPartOffset; 322 } 323 getTrieIndex()324 public int getTrieIndex() { 325 return this.power + UnitsData.Constants.kPowerPartOffset; 326 } 327 getValue()328 public int getValue() { 329 return power; 330 } 331 } 332 333 public enum InitialCompoundPart { 334 335 // Represents "per-", the only compound part that can appear at the start of 336 // an identifier. 337 INITIAL_COMPOUND_PART_PER(0); 338 339 private final int index; 340 InitialCompoundPart(int powerIndex)341 InitialCompoundPart(int powerIndex) { 342 this.index = powerIndex; 343 } 344 getInitialCompoundPartFromTrieIndex(int trieIndex)345 public static InitialCompoundPart getInitialCompoundPartFromTrieIndex(int trieIndex) { 346 int index = trieIndex - UnitsData.Constants.kInitialCompoundPartOffset; 347 if (index == 0) { 348 return INITIAL_COMPOUND_PART_PER; 349 } 350 351 throw new IllegalArgumentException("Incorrect trieIndex"); 352 } 353 getTrieIndex()354 public int getTrieIndex() { 355 return this.index + UnitsData.Constants.kInitialCompoundPartOffset; 356 } 357 getValue()358 public int getValue() { 359 return index; 360 } 361 362 } 363 364 public static class MeasureUnitImplWithIndex { 365 int index; 366 MeasureUnitImpl unitImpl; 367 MeasureUnitImplWithIndex(int index, MeasureUnitImpl unitImpl)368 MeasureUnitImplWithIndex(int index, MeasureUnitImpl unitImpl) { 369 this.index = index; 370 this.unitImpl = unitImpl; 371 } 372 } 373 374 public static class UnitsParser { 375 // This used only to not build the trie each time we use the parser 376 private volatile static CharsTrie savedTrie = null; 377 378 // This trie used in the parsing operation. 379 private final CharsTrie trie; 380 private final String fSource; 381 // Tracks parser progress: the offset into fSource. 382 private int fIndex = 0; 383 // Set to true when we've seen a "-per-" or a "per-", after which all units 384 // are in the denominator. Until we find an "-and-", at which point the 385 // identifier is invalid pending TODO(CLDR-13700). 386 private boolean fAfterPer = false; 387 // If an "-and-" was parsed prior to finding the "single 388 // * unit", sawAnd is set to true. If not, it is left as is. 389 private boolean fSawAnd = false; 390 391 // Cache the MeasurePrefix values array to make getPrefixFromTrieIndex() 392 // more efficient 393 private static MeasureUnit.MeasurePrefix[] measurePrefixValues = 394 MeasureUnit.MeasurePrefix.values(); 395 UnitsParser(String identifier)396 private UnitsParser(String identifier) { 397 this.fSource = identifier; 398 399 try { 400 this.trie = UnitsParser.savedTrie.clone(); 401 } catch (CloneNotSupportedException e) { 402 throw new ICUCloneNotSupportedException(); 403 } 404 } 405 406 static { 407 // Build Units trie. 408 CharsTrieBuilder trieBuilder; 409 trieBuilder = new CharsTrieBuilder(); 410 411 // Add SI and binary prefixes 412 for (MeasureUnit.MeasurePrefix unitPrefix : measurePrefixValues) { unitPrefix.getIdentifier()413 trieBuilder.add(unitPrefix.getIdentifier(), getTrieIndexForPrefix(unitPrefix)); 414 } 415 416 // Add syntax parts (compound, power prefixes) 417 trieBuilder.add("-per-", CompoundPart.PER.getTrieIndex()); 418 trieBuilder.add("-", CompoundPart.TIMES.getTrieIndex()); 419 trieBuilder.add("-and-", CompoundPart.AND.getTrieIndex()); 420 trieBuilder.add("per-", InitialCompoundPart.INITIAL_COMPOUND_PART_PER.getTrieIndex()); 421 trieBuilder.add("square-", PowerPart.P2.getTrieIndex()); 422 trieBuilder.add("cubic-", PowerPart.P3.getTrieIndex()); 423 trieBuilder.add("pow2-", PowerPart.P2.getTrieIndex()); 424 trieBuilder.add("pow3-", PowerPart.P3.getTrieIndex()); 425 trieBuilder.add("pow4-", PowerPart.P4.getTrieIndex()); 426 trieBuilder.add("pow5-", PowerPart.P5.getTrieIndex()); 427 trieBuilder.add("pow6-", PowerPart.P6.getTrieIndex()); 428 trieBuilder.add("pow7-", PowerPart.P7.getTrieIndex()); 429 trieBuilder.add("pow8-", PowerPart.P8.getTrieIndex()); 430 trieBuilder.add("pow9-", PowerPart.P9.getTrieIndex()); 431 trieBuilder.add("pow10-", PowerPart.P10.getTrieIndex()); 432 trieBuilder.add("pow11-", PowerPart.P11.getTrieIndex()); 433 trieBuilder.add("pow12-", PowerPart.P12.getTrieIndex()); 434 trieBuilder.add("pow13-", PowerPart.P13.getTrieIndex()); 435 trieBuilder.add("pow14-", PowerPart.P14.getTrieIndex()); 436 trieBuilder.add("pow15-", PowerPart.P15.getTrieIndex()); 437 438 // Add simple units 439 String[] simpleUnits = UnitsData.getSimpleUnits(); 440 for (int i = 0; i < simpleUnits.length; i++) { trieBuilder.add(simpleUnits[i], i + UnitsData.Constants.kSimpleUnitOffset)441 trieBuilder.add(simpleUnits[i], i + UnitsData.Constants.kSimpleUnitOffset); 442 443 } 444 445 // TODO: Use SLOW or FAST here? 446 UnitsParser.savedTrie = trieBuilder.build(StringTrieBuilder.Option.FAST); 447 } 448 449 /** 450 * Construct a MeasureUnit from a CLDR Unit Identifier, defined in UTS 35. 451 * Validates and canonicalizes the identifier. 452 * 453 * @return MeasureUnitImpl object or null if the identifier is empty. 454 * @throws IllegalArgumentException in case of invalid identifier. 455 */ parseForIdentifier(String identifier)456 public static MeasureUnitImpl parseForIdentifier(String identifier) { 457 if (identifier == null || identifier.isEmpty()) { 458 return null; 459 } 460 461 UnitsParser parser = new UnitsParser(identifier); 462 return parser.parse(); 463 464 } 465 getPrefixFromTrieIndex(int trieIndex)466 private static MeasureUnit.MeasurePrefix getPrefixFromTrieIndex(int trieIndex) { 467 return measurePrefixValues[trieIndex - UnitsData.Constants.kPrefixOffset]; 468 } 469 getTrieIndexForPrefix(MeasureUnit.MeasurePrefix prefix)470 private static int getTrieIndexForPrefix(MeasureUnit.MeasurePrefix prefix) { 471 return prefix.ordinal() + UnitsData.Constants.kPrefixOffset; 472 } 473 parse()474 private MeasureUnitImpl parse() { 475 MeasureUnitImpl result = new MeasureUnitImpl(); 476 477 if (fSource.isEmpty()) { 478 // The dimensionless unit: nothing to parse. return null. 479 return null; 480 } 481 482 while (hasNext()) { 483 fSawAnd = false; 484 SingleUnitImpl singleUnit = nextSingleUnit(); 485 486 boolean added = result.appendSingleUnit(singleUnit); 487 if (fSawAnd && !added) { 488 throw new IllegalArgumentException("Two similar units are not allowed in a mixed unit."); 489 } 490 491 if ((result.singleUnits.size()) >= 2) { 492 // nextSingleUnit fails appropriately for "per" and "and" in the 493 // same identifier. It doesn't fail for other compound units 494 // (COMPOUND_PART_TIMES). Consequently we take care of that 495 // here. 496 MeasureUnit.Complexity complexity = 497 fSawAnd ? MeasureUnit.Complexity.MIXED : MeasureUnit.Complexity.COMPOUND; 498 if (result.getSingleUnits().size() == 2) { 499 // After appending two singleUnits, the complexity will be MeasureUnit.Complexity.COMPOUND 500 assert result.getComplexity() == MeasureUnit.Complexity.COMPOUND; 501 result.setComplexity(complexity); 502 } else if (result.getComplexity() != complexity) { 503 throw new IllegalArgumentException("Can't have mixed compound units"); 504 } 505 } 506 } 507 508 return result; 509 } 510 511 /** 512 * Returns the next "single unit" via result. 513 * <p> 514 * If a "-per-" was parsed, the result will have appropriate negative 515 * dimensionality. 516 * <p> 517 * 518 * @throws IllegalArgumentException if we parse both compound units and "-and-", since mixed 519 * compound units are not yet supported - TODO(CLDR-13700). 520 */ nextSingleUnit()521 private SingleUnitImpl nextSingleUnit() { 522 SingleUnitImpl result = new SingleUnitImpl(); 523 524 // state: 525 // 0 = no tokens seen yet (will accept power, SI or binary prefix, or simple unit) 526 // 1 = power token seen (will not accept another power token) 527 // 2 = SI or binary prefix token seen (will not accept a power, or SI or binary prefix token) 528 int state = 0; 529 530 boolean atStart = fIndex == 0; 531 Token token = nextToken(); 532 533 if (atStart) { 534 // Identifiers optionally start with "per-". 535 if (token.getType() == Token.Type.TYPE_INITIAL_COMPOUND_PART) { 536 assert token.getInitialCompoundPart() == InitialCompoundPart.INITIAL_COMPOUND_PART_PER; 537 538 fAfterPer = true; 539 result.setDimensionality(-1); 540 541 token = nextToken(); 542 } 543 } else { 544 // All other SingleUnit's are separated from previous SingleUnit's 545 // via a compound part: 546 if (token.getType() != Token.Type.TYPE_COMPOUND_PART) { 547 throw new IllegalArgumentException("token type must be TYPE_COMPOUND_PART"); 548 } 549 550 CompoundPart compoundPart = CompoundPart.getCompoundPartFromTrieIndex(token.getMatch()); 551 switch (compoundPart) { 552 case PER: 553 if (fSawAnd) { 554 throw new IllegalArgumentException("Mixed compound units not yet supported"); 555 // TODO(CLDR-13700). 556 } 557 558 fAfterPer = true; 559 result.setDimensionality(-1); 560 break; 561 562 case TIMES: 563 if (fAfterPer) { 564 result.setDimensionality(-1); 565 } 566 break; 567 568 case AND: 569 if (fAfterPer) { 570 // not yet supported, TODO(CLDR-13700). 571 throw new IllegalArgumentException("Can't start with \"-and-\", and mixed compound units"); 572 } 573 fSawAnd = true; 574 break; 575 } 576 577 token = nextToken(); 578 } 579 580 // Read tokens until we have a complete SingleUnit or we reach the end. 581 while (true) { 582 switch (token.getType()) { 583 case TYPE_POWER_PART: 584 if (state > 0) { 585 throw new IllegalArgumentException(); 586 } 587 588 result.setDimensionality(result.getDimensionality() * token.getPower()); 589 state = 1; 590 break; 591 592 case TYPE_PREFIX: 593 if (state > 1) { 594 throw new IllegalArgumentException(); 595 } 596 597 result.setPrefix(token.getPrefix()); 598 state = 2; 599 break; 600 601 case TYPE_SIMPLE_UNIT: 602 result.setSimpleUnit(token.getSimpleUnitIndex(), UnitsData.getSimpleUnits()); 603 return result; 604 605 default: 606 throw new IllegalArgumentException(); 607 } 608 609 if (!hasNext()) { 610 throw new IllegalArgumentException("We ran out of tokens before finding a complete single unit."); 611 } 612 613 token = nextToken(); 614 } 615 } 616 hasNext()617 private boolean hasNext() { 618 return fIndex < fSource.length(); 619 } 620 nextToken()621 private Token nextToken() { 622 trie.reset(); 623 int match = -1; 624 // Saves the position in the fSource string for the end of the most 625 // recent matching token. 626 int previ = -1; 627 628 // Find the longest token that matches a value in the trie: 629 while (fIndex < fSource.length()) { 630 BytesTrie.Result result = trie.next(fSource.charAt(fIndex++)); 631 if (result == BytesTrie.Result.NO_MATCH) { 632 break; 633 } else if (result == BytesTrie.Result.NO_VALUE) { 634 continue; 635 } 636 637 match = trie.getValue(); 638 previ = fIndex; 639 640 if (result == BytesTrie.Result.FINAL_VALUE) { 641 break; 642 } 643 644 if (result != BytesTrie.Result.INTERMEDIATE_VALUE) { 645 throw new IllegalArgumentException("result must has an intermediate value"); 646 } 647 648 // continue; 649 } 650 651 652 if (match < 0) { 653 throw new IllegalArgumentException("Encountered unknown token starting at index " + previ); 654 } else { 655 fIndex = previ; 656 } 657 658 return new Token(match); 659 } 660 661 static class Token { 662 663 private final int fMatch; 664 private final Type type; 665 Token(int fMatch)666 public Token(int fMatch) { 667 this.fMatch = fMatch; 668 type = calculateType(fMatch); 669 } 670 getType()671 public Type getType() { 672 return this.type; 673 } 674 getPrefix()675 public MeasureUnit.MeasurePrefix getPrefix() { 676 assert this.type == Type.TYPE_PREFIX; 677 return getPrefixFromTrieIndex(this.fMatch); 678 } 679 680 // Valid only for tokens with type TYPE_COMPOUND_PART. getMatch()681 public int getMatch() { 682 assert getType() == Type.TYPE_COMPOUND_PART; 683 return fMatch; 684 } 685 686 // Even if there is only one InitialCompoundPart value, we have this 687 // function for the simplicity of code consistency. getInitialCompoundPart()688 public InitialCompoundPart getInitialCompoundPart() { 689 assert (this.type == Type.TYPE_INITIAL_COMPOUND_PART 690 && 691 fMatch == InitialCompoundPart.INITIAL_COMPOUND_PART_PER.getTrieIndex()); 692 return InitialCompoundPart.getInitialCompoundPartFromTrieIndex(fMatch); 693 } 694 getPower()695 public int getPower() { 696 assert this.type == Type.TYPE_POWER_PART; 697 return PowerPart.getPowerFromTrieIndex(this.fMatch); 698 } 699 getSimpleUnitIndex()700 public int getSimpleUnitIndex() { 701 assert this.type == Type.TYPE_SIMPLE_UNIT; 702 return this.fMatch - UnitsData.Constants.kSimpleUnitOffset; 703 } 704 705 // Calling calculateType() is invalid, resulting in an assertion failure, if Token 706 // value isn't positive. calculateType(int fMatch)707 private Type calculateType(int fMatch) { 708 if (fMatch <= 0) { 709 throw new AssertionError("fMatch must have a positive value"); 710 } 711 712 if (fMatch < UnitsData.Constants.kCompoundPartOffset) { 713 return Type.TYPE_PREFIX; 714 } 715 if (fMatch < UnitsData.Constants.kInitialCompoundPartOffset) { 716 return Type.TYPE_COMPOUND_PART; 717 } 718 if (fMatch < UnitsData.Constants.kPowerPartOffset) { 719 return Type.TYPE_INITIAL_COMPOUND_PART; 720 } 721 if (fMatch < UnitsData.Constants.kSimpleUnitOffset) { 722 return Type.TYPE_POWER_PART; 723 } 724 725 return Type.TYPE_SIMPLE_UNIT; 726 } 727 728 enum Type { 729 TYPE_UNDEFINED, 730 TYPE_PREFIX, 731 // Token type for "-per-", "-", and "-and-". 732 TYPE_COMPOUND_PART, 733 // Token type for "per-". 734 TYPE_INITIAL_COMPOUND_PART, 735 TYPE_POWER_PART, 736 TYPE_SIMPLE_UNIT, 737 } 738 } 739 } 740 741 static class MeasureUnitImplComparator implements Comparator<MeasureUnitImpl> { 742 private final ConversionRates conversionRates; 743 MeasureUnitImplComparator(ConversionRates conversionRates)744 public MeasureUnitImplComparator(ConversionRates conversionRates) { 745 this.conversionRates = conversionRates; 746 } 747 748 @Override compare(MeasureUnitImpl o1, MeasureUnitImpl o2)749 public int compare(MeasureUnitImpl o1, MeasureUnitImpl o2) { 750 BigDecimal factor1 = this.conversionRates.getFactorToBase(o1).getConversionRate(); 751 BigDecimal factor2 = this.conversionRates.getFactorToBase(o2).getConversionRate(); 752 753 return factor1.compareTo(factor2); 754 } 755 } 756 757 static class MeasureUnitImplWithIndexComparator implements Comparator<MeasureUnitImplWithIndex> { 758 private MeasureUnitImplComparator measureUnitImplComparator; 759 MeasureUnitImplWithIndexComparator(ConversionRates conversionRates)760 public MeasureUnitImplWithIndexComparator(ConversionRates conversionRates) { 761 this.measureUnitImplComparator = new MeasureUnitImplComparator(conversionRates); 762 } 763 764 @Override compare(MeasureUnitImplWithIndex o1, MeasureUnitImplWithIndex o2)765 public int compare(MeasureUnitImplWithIndex o1, MeasureUnitImplWithIndex o2) { 766 return this.measureUnitImplComparator.compare(o1.unitImpl, o2.unitImpl); 767 } 768 } 769 770 static class SingleUnitComparator implements Comparator<SingleUnitImpl> { 771 @Override compare(SingleUnitImpl o1, SingleUnitImpl o2)772 public int compare(SingleUnitImpl o1, SingleUnitImpl o2) { 773 return o1.compareTo(o2); 774 } 775 } 776 } 777