843 lines
29 KiB
Java
843 lines
29 KiB
Java
/* GENERATED SOURCE. DO NOT MODIFY. */
|
|
// © 2020 and later: Unicode, Inc. and others.
|
|
// License & terms of use: http://www.unicode.org/copyright.html
|
|
package android.icu.impl.units;
|
|
|
|
import java.math.BigDecimal;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
|
|
import android.icu.util.BytesTrie;
|
|
import android.icu.util.CharsTrie;
|
|
import android.icu.util.CharsTrieBuilder;
|
|
import android.icu.util.ICUCloneNotSupportedException;
|
|
import android.icu.util.MeasureUnit;
|
|
import android.icu.util.StringTrieBuilder;
|
|
|
|
/**
|
|
* @hide Only a subset of ICU is exposed in Android
|
|
*/
|
|
public class MeasureUnitImpl {
|
|
|
|
/**
|
|
* The full unit identifier. Null if not computed.
|
|
*/
|
|
private String identifier = null;
|
|
/**
|
|
* The complexity, either SINGLE, COMPOUND, or MIXED.
|
|
*/
|
|
private MeasureUnit.Complexity complexity = MeasureUnit.Complexity.SINGLE;
|
|
/**
|
|
* The list of single units. These may be summed or multiplied, based on the
|
|
* value of the complexity field.
|
|
* <p>
|
|
* The "dimensionless" unit (SingleUnitImpl default constructor) must not be added to this list.
|
|
* <p>
|
|
* The "dimensionless" <code>MeasureUnitImpl</code> has an empty <code>singleUnits</code>.
|
|
*/
|
|
private final ArrayList<SingleUnitImpl> singleUnits;
|
|
|
|
public MeasureUnitImpl() {
|
|
singleUnits = new ArrayList<>();
|
|
}
|
|
|
|
public MeasureUnitImpl(SingleUnitImpl singleUnit) {
|
|
this();
|
|
this.appendSingleUnit(singleUnit);
|
|
}
|
|
|
|
/**
|
|
* Parse a unit identifier into a MeasureUnitImpl.
|
|
*
|
|
* @param identifier The unit identifier string.
|
|
* @return A newly parsed object.
|
|
* @throws IllegalArgumentException in case of incorrect/non-parsed identifier.
|
|
*/
|
|
public static MeasureUnitImpl forIdentifier(String identifier) {
|
|
return UnitsParser.parseForIdentifier(identifier);
|
|
}
|
|
|
|
/**
|
|
* Used for currency units.
|
|
*/
|
|
public static MeasureUnitImpl forCurrencyCode(String currencyCode) {
|
|
MeasureUnitImpl result = new MeasureUnitImpl();
|
|
result.identifier = currencyCode;
|
|
return result;
|
|
}
|
|
|
|
public MeasureUnitImpl copy() {
|
|
MeasureUnitImpl result = new MeasureUnitImpl();
|
|
result.complexity = this.complexity;
|
|
result.identifier = this.identifier;
|
|
for (SingleUnitImpl singleUnit : this.singleUnits) {
|
|
result.singleUnits.add(singleUnit.copy());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Returns a simplified version of the unit.
|
|
* NOTE: the simplification happen when there are two units equals in their base unit and their
|
|
* prefixes.
|
|
*
|
|
* Example 1: "square-meter-per-meter" --> "meter"
|
|
* Example 2: "square-millimeter-per-meter" --> "square-millimeter-per-meter"
|
|
*/
|
|
public MeasureUnitImpl copyAndSimplify() {
|
|
MeasureUnitImpl result = new MeasureUnitImpl();
|
|
for (SingleUnitImpl singleUnit : this.getSingleUnits()) {
|
|
// This `for` loop will cause time complexity to be O(n^2).
|
|
// However, n is very small (number of units, generally, at maximum equal to 10)
|
|
boolean unitExist = false;
|
|
for (SingleUnitImpl resultSingleUnit : result.getSingleUnits()) {
|
|
if(resultSingleUnit.getSimpleUnitID().compareTo(singleUnit.getSimpleUnitID()) == 0
|
|
&&
|
|
resultSingleUnit.getPrefix().getIdentifier().compareTo(singleUnit.getPrefix().getIdentifier()) == 0
|
|
) {
|
|
unitExist = true;
|
|
resultSingleUnit.setDimensionality(resultSingleUnit.getDimensionality() + singleUnit.getDimensionality());
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!unitExist) {
|
|
result.appendSingleUnit(singleUnit);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Returns the list of simple units.
|
|
*/
|
|
public ArrayList<SingleUnitImpl> getSingleUnits() {
|
|
return singleUnits;
|
|
}
|
|
|
|
/**
|
|
* Mutates this MeasureUnitImpl to take the reciprocal.
|
|
*/
|
|
public void takeReciprocal() {
|
|
this.identifier = null;
|
|
for (SingleUnitImpl singleUnit :
|
|
this.singleUnits) {
|
|
singleUnit.setDimensionality(singleUnit.getDimensionality() * -1);
|
|
}
|
|
}
|
|
|
|
public ArrayList<MeasureUnitImplWithIndex> extractIndividualUnitsWithIndices() {
|
|
ArrayList<MeasureUnitImplWithIndex> result = new ArrayList<>();
|
|
if (this.getComplexity() == MeasureUnit.Complexity.MIXED) {
|
|
// In case of mixed units, each single unit can be considered as a stand alone MeasureUnitImpl.
|
|
int i = 0;
|
|
for (SingleUnitImpl singleUnit :
|
|
this.getSingleUnits()) {
|
|
result.add(new MeasureUnitImplWithIndex(i++, new MeasureUnitImpl(singleUnit)));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
result.add(new MeasureUnitImplWithIndex(0, this.copy()));
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Applies dimensionality to all the internal single units.
|
|
* For example: <b>square-meter-per-second</b>, when we apply dimensionality -2, it will be <b>square-second-per-p4-meter</b>
|
|
*/
|
|
public void applyDimensionality(int dimensionality) {
|
|
for (SingleUnitImpl singleUnit :
|
|
singleUnits) {
|
|
singleUnit.setDimensionality(singleUnit.getDimensionality() * dimensionality);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mutates this MeasureUnitImpl to append a single unit.
|
|
*
|
|
* @return true if a new item was added. If unit is the dimensionless unit,
|
|
* it is never added: the return value will always be false.
|
|
*/
|
|
public boolean appendSingleUnit(SingleUnitImpl singleUnit) {
|
|
identifier = null;
|
|
|
|
if (singleUnit == null) {
|
|
// Do not append dimensionless units.
|
|
return false;
|
|
}
|
|
|
|
// Find a similar unit that already exists, to attempt to coalesce
|
|
SingleUnitImpl oldUnit = null;
|
|
for (SingleUnitImpl candidate : this.singleUnits) {
|
|
if (candidate.isCompatibleWith(singleUnit)) {
|
|
oldUnit = candidate;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (oldUnit != null) {
|
|
// Both dimensionalities will be positive, or both will be negative, by
|
|
// virtue of isCompatibleWith().
|
|
oldUnit.setDimensionality(oldUnit.getDimensionality() + singleUnit.getDimensionality());
|
|
|
|
return false;
|
|
}
|
|
|
|
// Add a copy of singleUnit
|
|
this.singleUnits.add(singleUnit.copy());
|
|
|
|
// If the MeasureUnitImpl is `UMEASURE_UNIT_SINGLE` and after the appending a unit, the singleUnits contains
|
|
// more than one. thus means the complexity should be `UMEASURE_UNIT_COMPOUND`
|
|
if (this.singleUnits.size() > 1 && this.complexity == MeasureUnit.Complexity.SINGLE) {
|
|
this.setComplexity(MeasureUnit.Complexity.COMPOUND);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Transform this MeasureUnitImpl into a MeasureUnit, simplifying if possible.
|
|
* <p>
|
|
* NOTE: this function must be called from a thread-safe class
|
|
*/
|
|
public MeasureUnit build() {
|
|
return MeasureUnit.fromMeasureUnitImpl(this);
|
|
}
|
|
|
|
/**
|
|
* @return SingleUnitImpl
|
|
* @throws UnsupportedOperationException if the object could not be converted to SingleUnitImpl.
|
|
*/
|
|
public SingleUnitImpl getSingleUnitImpl() {
|
|
if (this.singleUnits.size() == 0) {
|
|
return new SingleUnitImpl();
|
|
}
|
|
if (this.singleUnits.size() == 1) {
|
|
return this.singleUnits.get(0).copy();
|
|
}
|
|
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
/**
|
|
* Returns the CLDR unit identifier and null if not computed.
|
|
*/
|
|
public String getIdentifier() {
|
|
return identifier;
|
|
}
|
|
|
|
public MeasureUnit.Complexity getComplexity() {
|
|
return complexity;
|
|
}
|
|
|
|
public void setComplexity(MeasureUnit.Complexity complexity) {
|
|
this.complexity = complexity;
|
|
}
|
|
|
|
/**
|
|
* Normalizes the MeasureUnitImpl and generates the identifier string in place.
|
|
*/
|
|
public void serialize() {
|
|
if (this.getSingleUnits().size() == 0) {
|
|
// Dimensionless, constructed by the default constructor: no appending
|
|
// to this.result, we wish it to contain the zero-length string.
|
|
return;
|
|
}
|
|
|
|
|
|
if (this.complexity == MeasureUnit.Complexity.COMPOUND) {
|
|
// Note: don't sort a MIXED unit
|
|
Collections.sort(this.getSingleUnits(), new SingleUnitComparator());
|
|
}
|
|
|
|
StringBuilder result = new StringBuilder();
|
|
boolean beforePer = true;
|
|
boolean firstTimeNegativeDimension = false;
|
|
for (SingleUnitImpl singleUnit :
|
|
this.getSingleUnits()) {
|
|
if (beforePer && singleUnit.getDimensionality() < 0) {
|
|
beforePer = false;
|
|
firstTimeNegativeDimension = true;
|
|
} else if (singleUnit.getDimensionality() < 0) {
|
|
firstTimeNegativeDimension = false;
|
|
}
|
|
|
|
if (this.getComplexity() == MeasureUnit.Complexity.MIXED) {
|
|
if (result.length() != 0) {
|
|
result.append("-and-");
|
|
}
|
|
} else {
|
|
if (firstTimeNegativeDimension) {
|
|
if (result.length() == 0) {
|
|
result.append("per-");
|
|
} else {
|
|
result.append("-per-");
|
|
}
|
|
} else {
|
|
if (result.length() != 0) {
|
|
result.append("-");
|
|
}
|
|
}
|
|
}
|
|
|
|
result.append(singleUnit.getNeutralIdentifier());
|
|
}
|
|
|
|
this.identifier = result.toString();
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "MeasureUnitImpl [" + build().getIdentifier() + "]";
|
|
}
|
|
|
|
/**
|
|
* @hide Only a subset of ICU is exposed in Android
|
|
*/
|
|
public enum CompoundPart {
|
|
// Represents "-per-"
|
|
PER(0),
|
|
// Represents "-"
|
|
TIMES(1),
|
|
// Represents "-and-"
|
|
AND(2);
|
|
|
|
private final int index;
|
|
|
|
CompoundPart(int index) {
|
|
this.index = index;
|
|
}
|
|
|
|
public static CompoundPart getCompoundPartFromTrieIndex(int trieIndex) {
|
|
int index = trieIndex - UnitsData.Constants.kCompoundPartOffset;
|
|
switch (index) {
|
|
case 0:
|
|
return CompoundPart.PER;
|
|
case 1:
|
|
return CompoundPart.TIMES;
|
|
case 2:
|
|
return CompoundPart.AND;
|
|
default:
|
|
throw new AssertionError("CompoundPart index must be 0, 1 or 2");
|
|
}
|
|
}
|
|
|
|
public int getTrieIndex() {
|
|
return this.index + UnitsData.Constants.kCompoundPartOffset;
|
|
}
|
|
|
|
public int getValue() {
|
|
return index;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @hide Only a subset of ICU is exposed in Android
|
|
*/
|
|
public enum PowerPart {
|
|
P2(2),
|
|
P3(3),
|
|
P4(4),
|
|
P5(5),
|
|
P6(6),
|
|
P7(7),
|
|
P8(8),
|
|
P9(9),
|
|
P10(10),
|
|
P11(11),
|
|
P12(12),
|
|
P13(13),
|
|
P14(14),
|
|
P15(15);
|
|
|
|
private final int power;
|
|
|
|
PowerPart(int power) {
|
|
this.power = power;
|
|
}
|
|
|
|
public static int getPowerFromTrieIndex(int trieIndex) {
|
|
return trieIndex - UnitsData.Constants.kPowerPartOffset;
|
|
}
|
|
|
|
public int getTrieIndex() {
|
|
return this.power + UnitsData.Constants.kPowerPartOffset;
|
|
}
|
|
|
|
public int getValue() {
|
|
return power;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @hide Only a subset of ICU is exposed in Android
|
|
*/
|
|
public enum InitialCompoundPart {
|
|
|
|
// Represents "per-", the only compound part that can appear at the start of
|
|
// an identifier.
|
|
INITIAL_COMPOUND_PART_PER(0);
|
|
|
|
private final int index;
|
|
|
|
InitialCompoundPart(int powerIndex) {
|
|
this.index = powerIndex;
|
|
}
|
|
|
|
public static InitialCompoundPart getInitialCompoundPartFromTrieIndex(int trieIndex) {
|
|
int index = trieIndex - UnitsData.Constants.kInitialCompoundPartOffset;
|
|
if (index == 0) {
|
|
return INITIAL_COMPOUND_PART_PER;
|
|
}
|
|
|
|
throw new IllegalArgumentException("Incorrect trieIndex");
|
|
}
|
|
|
|
public int getTrieIndex() {
|
|
return this.index + UnitsData.Constants.kInitialCompoundPartOffset;
|
|
}
|
|
|
|
public int getValue() {
|
|
return index;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* @hide Only a subset of ICU is exposed in Android
|
|
*/
|
|
public static class MeasureUnitImplWithIndex {
|
|
int index;
|
|
MeasureUnitImpl unitImpl;
|
|
|
|
MeasureUnitImplWithIndex(int index, MeasureUnitImpl unitImpl) {
|
|
this.index = index;
|
|
this.unitImpl = unitImpl;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @hide Only a subset of ICU is exposed in Android
|
|
*/
|
|
public static class UnitsParser {
|
|
// This used only to not build the trie each time we use the parser
|
|
private volatile static CharsTrie savedTrie = null;
|
|
|
|
// This trie used in the parsing operation.
|
|
private final CharsTrie trie;
|
|
private final String fSource;
|
|
// Tracks parser progress: the offset into fSource.
|
|
private int fIndex = 0;
|
|
// Set to true when we've seen a "-per-" or a "per-", after which all units
|
|
// are in the denominator. Until we find an "-and-", at which point the
|
|
// identifier is invalid pending TODO(CLDR-13701).
|
|
private boolean fAfterPer = false;
|
|
// If an "-and-" was parsed prior to finding the "single
|
|
// * unit", sawAnd is set to true. If not, it is left as is.
|
|
private boolean fSawAnd = false;
|
|
|
|
// Cache the MeasurePrefix values array to make getPrefixFromTrieIndex()
|
|
// more efficient
|
|
private static MeasureUnit.MeasurePrefix[] measurePrefixValues =
|
|
MeasureUnit.MeasurePrefix.values();
|
|
|
|
private UnitsParser(String identifier) {
|
|
this.fSource = identifier;
|
|
|
|
try {
|
|
this.trie = UnitsParser.savedTrie.clone();
|
|
} catch (CloneNotSupportedException e) {
|
|
throw new ICUCloneNotSupportedException();
|
|
}
|
|
}
|
|
|
|
static {
|
|
// Build Units trie.
|
|
CharsTrieBuilder trieBuilder;
|
|
trieBuilder = new CharsTrieBuilder();
|
|
|
|
// Add SI and binary prefixes
|
|
for (MeasureUnit.MeasurePrefix unitPrefix : measurePrefixValues) {
|
|
trieBuilder.add(unitPrefix.getIdentifier(), getTrieIndexForPrefix(unitPrefix));
|
|
}
|
|
|
|
// Add syntax parts (compound, power prefixes)
|
|
trieBuilder.add("-per-", CompoundPart.PER.getTrieIndex());
|
|
trieBuilder.add("-", CompoundPart.TIMES.getTrieIndex());
|
|
trieBuilder.add("-and-", CompoundPart.AND.getTrieIndex());
|
|
trieBuilder.add("per-", InitialCompoundPart.INITIAL_COMPOUND_PART_PER.getTrieIndex());
|
|
trieBuilder.add("square-", PowerPart.P2.getTrieIndex());
|
|
trieBuilder.add("cubic-", PowerPart.P3.getTrieIndex());
|
|
trieBuilder.add("pow2-", PowerPart.P2.getTrieIndex());
|
|
trieBuilder.add("pow3-", PowerPart.P3.getTrieIndex());
|
|
trieBuilder.add("pow4-", PowerPart.P4.getTrieIndex());
|
|
trieBuilder.add("pow5-", PowerPart.P5.getTrieIndex());
|
|
trieBuilder.add("pow6-", PowerPart.P6.getTrieIndex());
|
|
trieBuilder.add("pow7-", PowerPart.P7.getTrieIndex());
|
|
trieBuilder.add("pow8-", PowerPart.P8.getTrieIndex());
|
|
trieBuilder.add("pow9-", PowerPart.P9.getTrieIndex());
|
|
trieBuilder.add("pow10-", PowerPart.P10.getTrieIndex());
|
|
trieBuilder.add("pow11-", PowerPart.P11.getTrieIndex());
|
|
trieBuilder.add("pow12-", PowerPart.P12.getTrieIndex());
|
|
trieBuilder.add("pow13-", PowerPart.P13.getTrieIndex());
|
|
trieBuilder.add("pow14-", PowerPart.P14.getTrieIndex());
|
|
trieBuilder.add("pow15-", PowerPart.P15.getTrieIndex());
|
|
|
|
// Add simple units
|
|
String[] simpleUnits = UnitsData.getSimpleUnits();
|
|
for (int i = 0; i < simpleUnits.length; i++) {
|
|
trieBuilder.add(simpleUnits[i], i + UnitsData.Constants.kSimpleUnitOffset);
|
|
|
|
}
|
|
|
|
// TODO: Use SLOW or FAST here?
|
|
UnitsParser.savedTrie = trieBuilder.build(StringTrieBuilder.Option.FAST);
|
|
}
|
|
|
|
/**
|
|
* Construct a MeasureUnit from a CLDR Unit Identifier, defined in UTS 35.
|
|
* Validates and canonicalizes the identifier.
|
|
*
|
|
* @return MeasureUnitImpl object or null if the identifier is empty.
|
|
* @throws IllegalArgumentException in case of invalid identifier.
|
|
*/
|
|
public static MeasureUnitImpl parseForIdentifier(String identifier) {
|
|
if (identifier == null || identifier.isEmpty()) {
|
|
return null;
|
|
}
|
|
|
|
UnitsParser parser = new UnitsParser(identifier);
|
|
return parser.parse();
|
|
|
|
}
|
|
|
|
private static MeasureUnit.MeasurePrefix getPrefixFromTrieIndex(int trieIndex) {
|
|
return measurePrefixValues[trieIndex - UnitsData.Constants.kPrefixOffset];
|
|
}
|
|
|
|
private static int getTrieIndexForPrefix(MeasureUnit.MeasurePrefix prefix) {
|
|
return prefix.ordinal() + UnitsData.Constants.kPrefixOffset;
|
|
}
|
|
|
|
private MeasureUnitImpl parse() {
|
|
MeasureUnitImpl result = new MeasureUnitImpl();
|
|
|
|
if (fSource.isEmpty()) {
|
|
// The dimensionless unit: nothing to parse. return null.
|
|
return null;
|
|
}
|
|
|
|
while (hasNext()) {
|
|
fSawAnd = false;
|
|
SingleUnitImpl singleUnit = nextSingleUnit();
|
|
|
|
boolean added = result.appendSingleUnit(singleUnit);
|
|
if (fSawAnd && !added) {
|
|
throw new IllegalArgumentException("Two similar units are not allowed in a mixed unit.");
|
|
}
|
|
|
|
if ((result.singleUnits.size()) >= 2) {
|
|
// nextSingleUnit fails appropriately for "per" and "and" in the
|
|
// same identifier. It doesn't fail for other compound units
|
|
// (COMPOUND_PART_TIMES). Consequently we take care of that
|
|
// here.
|
|
MeasureUnit.Complexity complexity =
|
|
fSawAnd ? MeasureUnit.Complexity.MIXED : MeasureUnit.Complexity.COMPOUND;
|
|
if (result.getSingleUnits().size() == 2) {
|
|
// After appending two singleUnits, the complexity will be MeasureUnit.Complexity.COMPOUND
|
|
assert result.getComplexity() == MeasureUnit.Complexity.COMPOUND;
|
|
result.setComplexity(complexity);
|
|
} else if (result.getComplexity() != complexity) {
|
|
throw new IllegalArgumentException("Can't have mixed compound units");
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Returns the next "single unit" via result.
|
|
* <p>
|
|
* If a "-per-" was parsed, the result will have appropriate negative
|
|
* dimensionality.
|
|
* <p>
|
|
*
|
|
* @throws IllegalArgumentException if we parse both compound units and "-and-", since mixed
|
|
* compound units are not yet supported - TODO(CLDR-13701).
|
|
*/
|
|
private SingleUnitImpl nextSingleUnit() {
|
|
SingleUnitImpl result = new SingleUnitImpl();
|
|
|
|
// state:
|
|
// 0 = no tokens seen yet (will accept power, SI or binary prefix, or simple unit)
|
|
// 1 = power token seen (will not accept another power token)
|
|
// 2 = SI or binary prefix token seen (will not accept a power, or SI or binary prefix token)
|
|
int state = 0;
|
|
|
|
boolean atStart = fIndex == 0;
|
|
Token token = nextToken();
|
|
|
|
if (atStart) {
|
|
// Identifiers optionally start with "per-".
|
|
if (token.getType() == Token.Type.TYPE_INITIAL_COMPOUND_PART) {
|
|
assert token.getInitialCompoundPart() == InitialCompoundPart.INITIAL_COMPOUND_PART_PER;
|
|
|
|
fAfterPer = true;
|
|
result.setDimensionality(-1);
|
|
|
|
token = nextToken();
|
|
}
|
|
} else {
|
|
// All other SingleUnit's are separated from previous SingleUnit's
|
|
// via a compound part:
|
|
if (token.getType() != Token.Type.TYPE_COMPOUND_PART) {
|
|
throw new IllegalArgumentException("token type must be TYPE_COMPOUND_PART");
|
|
}
|
|
|
|
CompoundPart compoundPart = CompoundPart.getCompoundPartFromTrieIndex(token.getMatch());
|
|
switch (compoundPart) {
|
|
case PER:
|
|
if (fSawAnd) {
|
|
throw new IllegalArgumentException("Mixed compound units not yet supported");
|
|
// TODO(CLDR-13701).
|
|
}
|
|
|
|
fAfterPer = true;
|
|
result.setDimensionality(-1);
|
|
break;
|
|
|
|
case TIMES:
|
|
if (fAfterPer) {
|
|
result.setDimensionality(-1);
|
|
}
|
|
break;
|
|
|
|
case AND:
|
|
if (fAfterPer) {
|
|
// not yet supported, TODO(CLDR-13701).
|
|
throw new IllegalArgumentException("Can't start with \"-and-\", and mixed compound units");
|
|
}
|
|
fSawAnd = true;
|
|
break;
|
|
}
|
|
|
|
token = nextToken();
|
|
}
|
|
|
|
// Read tokens until we have a complete SingleUnit or we reach the end.
|
|
while (true) {
|
|
switch (token.getType()) {
|
|
case TYPE_POWER_PART:
|
|
if (state > 0) {
|
|
throw new IllegalArgumentException();
|
|
}
|
|
|
|
result.setDimensionality(result.getDimensionality() * token.getPower());
|
|
state = 1;
|
|
break;
|
|
|
|
case TYPE_PREFIX:
|
|
if (state > 1) {
|
|
throw new IllegalArgumentException();
|
|
}
|
|
|
|
result.setPrefix(token.getPrefix());
|
|
state = 2;
|
|
break;
|
|
|
|
case TYPE_SIMPLE_UNIT:
|
|
result.setSimpleUnit(token.getSimpleUnitIndex(), UnitsData.getSimpleUnits());
|
|
return result;
|
|
|
|
default:
|
|
throw new IllegalArgumentException();
|
|
}
|
|
|
|
if (!hasNext()) {
|
|
throw new IllegalArgumentException("We ran out of tokens before finding a complete single unit.");
|
|
}
|
|
|
|
token = nextToken();
|
|
}
|
|
}
|
|
|
|
private boolean hasNext() {
|
|
return fIndex < fSource.length();
|
|
}
|
|
|
|
private Token nextToken() {
|
|
trie.reset();
|
|
int match = -1;
|
|
// Saves the position in the fSource string for the end of the most
|
|
// recent matching token.
|
|
int previ = -1;
|
|
|
|
// Find the longest token that matches a value in the trie:
|
|
while (fIndex < fSource.length()) {
|
|
BytesTrie.Result result = trie.next(fSource.charAt(fIndex++));
|
|
if (result == BytesTrie.Result.NO_MATCH) {
|
|
break;
|
|
} else if (result == BytesTrie.Result.NO_VALUE) {
|
|
continue;
|
|
}
|
|
|
|
match = trie.getValue();
|
|
previ = fIndex;
|
|
|
|
if (result == BytesTrie.Result.FINAL_VALUE) {
|
|
break;
|
|
}
|
|
|
|
if (result != BytesTrie.Result.INTERMEDIATE_VALUE) {
|
|
throw new IllegalArgumentException("result must has an intermediate value");
|
|
}
|
|
|
|
// continue;
|
|
}
|
|
|
|
|
|
if (match < 0) {
|
|
throw new IllegalArgumentException("Encountered unknown token starting at index " + previ);
|
|
} else {
|
|
fIndex = previ;
|
|
}
|
|
|
|
return new Token(match);
|
|
}
|
|
|
|
static class Token {
|
|
|
|
private final int fMatch;
|
|
private final Type type;
|
|
|
|
public Token(int fMatch) {
|
|
this.fMatch = fMatch;
|
|
type = calculateType(fMatch);
|
|
}
|
|
|
|
public Type getType() {
|
|
return this.type;
|
|
}
|
|
|
|
public MeasureUnit.MeasurePrefix getPrefix() {
|
|
assert this.type == Type.TYPE_PREFIX;
|
|
return getPrefixFromTrieIndex(this.fMatch);
|
|
}
|
|
|
|
// Valid only for tokens with type TYPE_COMPOUND_PART.
|
|
public int getMatch() {
|
|
assert getType() == Type.TYPE_COMPOUND_PART;
|
|
return fMatch;
|
|
}
|
|
|
|
// Even if there is only one InitialCompoundPart value, we have this
|
|
// function for the simplicity of code consistency.
|
|
public InitialCompoundPart getInitialCompoundPart() {
|
|
assert (this.type == Type.TYPE_INITIAL_COMPOUND_PART
|
|
&&
|
|
fMatch == InitialCompoundPart.INITIAL_COMPOUND_PART_PER.getTrieIndex());
|
|
return InitialCompoundPart.getInitialCompoundPartFromTrieIndex(fMatch);
|
|
}
|
|
|
|
public int getPower() {
|
|
assert this.type == Type.TYPE_POWER_PART;
|
|
return PowerPart.getPowerFromTrieIndex(this.fMatch);
|
|
}
|
|
|
|
public int getSimpleUnitIndex() {
|
|
assert this.type == Type.TYPE_SIMPLE_UNIT;
|
|
return this.fMatch - UnitsData.Constants.kSimpleUnitOffset;
|
|
}
|
|
|
|
// Calling calculateType() is invalid, resulting in an assertion failure, if Token
|
|
// value isn't positive.
|
|
private Type calculateType(int fMatch) {
|
|
if (fMatch <= 0) {
|
|
throw new AssertionError("fMatch must have a positive value");
|
|
}
|
|
|
|
if (fMatch < UnitsData.Constants.kCompoundPartOffset) {
|
|
return Type.TYPE_PREFIX;
|
|
}
|
|
if (fMatch < UnitsData.Constants.kInitialCompoundPartOffset) {
|
|
return Type.TYPE_COMPOUND_PART;
|
|
}
|
|
if (fMatch < UnitsData.Constants.kPowerPartOffset) {
|
|
return Type.TYPE_INITIAL_COMPOUND_PART;
|
|
}
|
|
if (fMatch < UnitsData.Constants.kSimpleUnitOffset) {
|
|
return Type.TYPE_POWER_PART;
|
|
}
|
|
|
|
return Type.TYPE_SIMPLE_UNIT;
|
|
}
|
|
|
|
enum Type {
|
|
TYPE_UNDEFINED,
|
|
TYPE_PREFIX,
|
|
// Token type for "-per-", "-", and "-and-".
|
|
TYPE_COMPOUND_PART,
|
|
// Token type for "per-".
|
|
TYPE_INITIAL_COMPOUND_PART,
|
|
TYPE_POWER_PART,
|
|
TYPE_SIMPLE_UNIT,
|
|
}
|
|
}
|
|
}
|
|
|
|
static class MeasureUnitImplComparator implements Comparator<MeasureUnitImpl> {
|
|
private final ConversionRates conversionRates;
|
|
|
|
public MeasureUnitImplComparator(ConversionRates conversionRates) {
|
|
this.conversionRates = conversionRates;
|
|
}
|
|
|
|
@Override
|
|
public int compare(MeasureUnitImpl o1, MeasureUnitImpl o2) {
|
|
String special1 = this.conversionRates.getSpecialMappingName(o1);
|
|
String special2 = this.conversionRates.getSpecialMappingName(o2);
|
|
if (special1 != null || special2 != null) {
|
|
if (special1 == null) {
|
|
// non-specials come first
|
|
return -1;
|
|
}
|
|
if (special2 == null) {
|
|
// non-specials come first
|
|
return 1;
|
|
}
|
|
// both are specials, compare lexicographically
|
|
return special1.compareTo(special2);
|
|
}
|
|
BigDecimal factor1 = this.conversionRates.getFactorToBase(o1).getConversionRate();
|
|
BigDecimal factor2 = this.conversionRates.getFactorToBase(o2).getConversionRate();
|
|
|
|
return factor1.compareTo(factor2);
|
|
}
|
|
}
|
|
|
|
static class MeasureUnitImplWithIndexComparator implements Comparator<MeasureUnitImplWithIndex> {
|
|
private MeasureUnitImplComparator measureUnitImplComparator;
|
|
|
|
public MeasureUnitImplWithIndexComparator(ConversionRates conversionRates) {
|
|
this.measureUnitImplComparator = new MeasureUnitImplComparator(conversionRates);
|
|
}
|
|
|
|
@Override
|
|
public int compare(MeasureUnitImplWithIndex o1, MeasureUnitImplWithIndex o2) {
|
|
return this.measureUnitImplComparator.compare(o1.unitImpl, o2.unitImpl);
|
|
}
|
|
}
|
|
|
|
static class SingleUnitComparator implements Comparator<SingleUnitImpl> {
|
|
@Override
|
|
public int compare(SingleUnitImpl o1, SingleUnitImpl o2) {
|
|
return o1.compareTo(o2);
|
|
}
|
|
}
|
|
}
|