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