• 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 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