mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-06 22:15:31 +00:00
parent
384c54ce66
commit
373cbaf3b2
3 changed files with 428 additions and 89 deletions
|
@ -18,6 +18,7 @@ import java.lang.reflect.Field;
|
|||
import java.text.FieldPosition;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
|
@ -49,6 +50,7 @@ import com.ibm.icu.util.NoUnit;
|
|||
import com.ibm.icu.util.TimeUnit;
|
||||
import com.ibm.icu.util.TimeUnitAmount;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
import com.ibm.icu.util.MeasureUnit.Complexity;
|
||||
|
||||
/**
|
||||
* This file contains regular unit tests.
|
||||
|
@ -1145,10 +1147,12 @@ public class MeasureUnitTest extends CoreTestFmwk {
|
|||
}
|
||||
System.out.println("MeasureFormatHandler.hasSameBehavior fails:");
|
||||
if (!getLocaleEqual) {
|
||||
System.out.println("- getLocale equality fails: old a1: " + a1.getLocale().getName() + "; test b1: " + b1.getLocale().getName());
|
||||
System.out.println("- getLocale equality fails: old a1: " + a1.getLocale().getName() + "; test b1: "
|
||||
+ b1.getLocale().getName());
|
||||
}
|
||||
if (!getWidthEqual) {
|
||||
System.out.println("- getWidth equality fails: old a1: " + a1.getWidth().name() + "; test b1: " + b1.getWidth().name());
|
||||
System.out.println("- getWidth equality fails: old a1: " + a1.getWidth().name() + "; test b1: "
|
||||
+ b1.getWidth().name());
|
||||
}
|
||||
if (!numFmtHasSameBehavior) {
|
||||
System.out.println("- getNumberFormat hasSameBehavior fails");
|
||||
|
@ -1311,6 +1315,83 @@ public class MeasureUnitTest extends CoreTestFmwk {
|
|||
null, MeasureUnit.forIdentifier(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestAcceptableConstantDenominator() {
|
||||
class ConstantDenominatorTestCase {
|
||||
String identifier;
|
||||
long expectedConstantDenominator;
|
||||
|
||||
ConstantDenominatorTestCase(String identifier, long expectedConstantDenominator) {
|
||||
this.identifier = identifier;
|
||||
this.expectedConstantDenominator = expectedConstantDenominator;
|
||||
}
|
||||
}
|
||||
|
||||
List<ConstantDenominatorTestCase> testCases = Arrays.asList(
|
||||
new ConstantDenominatorTestCase("meter-per-1000", 1000),
|
||||
new ConstantDenominatorTestCase("liter-per-1000-kiloliter", 1000),
|
||||
new ConstantDenominatorTestCase("liter-per-kilometer", 0),
|
||||
new ConstantDenominatorTestCase("second-per-1000-minute", 1000),
|
||||
new ConstantDenominatorTestCase("gram-per-1000-kilogram", 1000),
|
||||
new ConstantDenominatorTestCase("meter-per-100", 100),
|
||||
// Test for constant denominators that are powers of 10
|
||||
new ConstantDenominatorTestCase("portion-per-1", 1),
|
||||
new ConstantDenominatorTestCase("portion-per-10", 10),
|
||||
new ConstantDenominatorTestCase("portion-per-100", 100),
|
||||
new ConstantDenominatorTestCase("portion-per-1000", 1000),
|
||||
new ConstantDenominatorTestCase("portion-per-10000", 10000),
|
||||
new ConstantDenominatorTestCase("portion-per-100000", 100000),
|
||||
new ConstantDenominatorTestCase("portion-per-1000000", 1000000),
|
||||
new ConstantDenominatorTestCase("portion-per-10000000", 10000000),
|
||||
new ConstantDenominatorTestCase("portion-per-100000000", 100000000),
|
||||
new ConstantDenominatorTestCase("portion-per-1000000000", 1000000000),
|
||||
new ConstantDenominatorTestCase("portion-per-10000000000", 10000000000L),
|
||||
new ConstantDenominatorTestCase("portion-per-100000000000", 100000000000L),
|
||||
new ConstantDenominatorTestCase("portion-per-1000000000000", 1000000000000L),
|
||||
new ConstantDenominatorTestCase("portion-per-10000000000000", 10000000000000L),
|
||||
new ConstantDenominatorTestCase("portion-per-100000000000000", 100000000000000L),
|
||||
new ConstantDenominatorTestCase("portion-per-1000000000000000", 1000000000000000L),
|
||||
new ConstantDenominatorTestCase("portion-per-10000000000000000", 10000000000000000L),
|
||||
new ConstantDenominatorTestCase("portion-per-100000000000000000", 100000000000000000L),
|
||||
new ConstantDenominatorTestCase("portion-per-1000000000000000000", 1000000000000000000L),
|
||||
// Test for constant denominators that are represented as scientific notation
|
||||
// numbers.
|
||||
new ConstantDenominatorTestCase("portion-per-1e9", 1000000000L),
|
||||
new ConstantDenominatorTestCase("portion-per-1E9", 1000000000L),
|
||||
new ConstantDenominatorTestCase("portion-per-10e9", 10000000000L),
|
||||
new ConstantDenominatorTestCase("portion-per-10E9", 10000000000L),
|
||||
new ConstantDenominatorTestCase("portion-per-1e10", 10000000000L),
|
||||
new ConstantDenominatorTestCase("portion-per-1E10", 10000000000L),
|
||||
new ConstantDenominatorTestCase("portion-per-1e3-kilometer", 1000),
|
||||
// Test for constant denominators that are randomely selected.
|
||||
new ConstantDenominatorTestCase("liter-per-12345-kilometer", 12345),
|
||||
new ConstantDenominatorTestCase("per-1000-kilometer", 1000),
|
||||
new ConstantDenominatorTestCase("liter-per-1000-kiloliter", 1000),
|
||||
// Test for constant denominators that gives 0.
|
||||
new ConstantDenominatorTestCase("meter", 0),
|
||||
new ConstantDenominatorTestCase("meter-per-second", 0),
|
||||
new ConstantDenominatorTestCase("meter-per-square-second", 0),
|
||||
// NOTE: The following constant denominator should be 0. However, since
|
||||
// `100-kilometer` is treated as a unit in CLDR,
|
||||
// the unit does not have a constant denominator.
|
||||
// This issue should be addressed in CLDR.
|
||||
new ConstantDenominatorTestCase("meter-per-100-kilometer", 0),
|
||||
// NOTE: the following CLDR identifier should be invalid, but because
|
||||
// `100-kilometer` is considered a unit in CLDR,
|
||||
// one `100` will be considered as a unit constant denominator and the other
|
||||
// `100` will be considered part of the unit.
|
||||
// This issue should be addressed in CLDR.
|
||||
new ConstantDenominatorTestCase("meter-per-100-100-kilometer", 100));
|
||||
|
||||
for (ConstantDenominatorTestCase testCase : testCases) {
|
||||
MeasureUnit unit = MeasureUnit.forIdentifier(testCase.identifier);
|
||||
assertEquals("Constant denominator for " + testCase.identifier, testCase.expectedConstantDenominator,
|
||||
unit.getConstantDenominator());
|
||||
assertTrue("Complexity for " + testCase.identifier,
|
||||
unit.getComplexity() == Complexity.COMPOUND || unit.getComplexity() == Complexity.SINGLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestInvalidIdentifiers() {
|
||||
final String inputs[] = {
|
||||
|
@ -1348,6 +1429,19 @@ public class MeasureUnitTest extends CoreTestFmwk {
|
|||
|
||||
// Compound units not supported in mixed units yet. TODO(CLDR-13701).
|
||||
"kilonewton-meter-and-newton-meter",
|
||||
|
||||
// Invalid units because of invalid constant denominator
|
||||
"meter-per--20--second",
|
||||
"meter-per-1000-1e9-second",
|
||||
"meter-per-1e20-second",
|
||||
"per-1000",
|
||||
"meter-per-1000-1000",
|
||||
"meter-per-1000-second-1000-kilometer",
|
||||
"1000-meter",
|
||||
"meter-1000",
|
||||
"meter-per-1000-1000",
|
||||
"meter-per-1000-second-1000-kilometer",
|
||||
"per-1000-and-per-1000",
|
||||
};
|
||||
|
||||
for (String input : inputs) {
|
||||
|
|
|
@ -14,6 +14,7 @@ import com.ibm.icu.util.ICUCloneNotSupportedException;
|
|||
import com.ibm.icu.util.MeasureUnit;
|
||||
import com.ibm.icu.util.StringTrieBuilder;
|
||||
|
||||
|
||||
public class MeasureUnitImpl {
|
||||
|
||||
/**
|
||||
|
@ -24,6 +25,12 @@ public class MeasureUnitImpl {
|
|||
* The complexity, either SINGLE, COMPOUND, or MIXED.
|
||||
*/
|
||||
private MeasureUnit.Complexity complexity = MeasureUnit.Complexity.SINGLE;
|
||||
/**
|
||||
* The constant denominator.
|
||||
*
|
||||
* NOTE: when it is 0, it means there is no constant denominator.
|
||||
*/
|
||||
private long constantDenominator = 0;
|
||||
/**
|
||||
* The list of single units. These may be summed or multiplied, based on the
|
||||
* value of the complexity field.
|
||||
|
@ -67,6 +74,7 @@ public class MeasureUnitImpl {
|
|||
MeasureUnitImpl result = new MeasureUnitImpl();
|
||||
result.complexity = this.complexity;
|
||||
result.identifier = this.identifier;
|
||||
result.constantDenominator = this.constantDenominator;
|
||||
for (SingleUnitImpl singleUnit : this.singleUnits) {
|
||||
result.singleUnits.add(singleUnit.copy());
|
||||
}
|
||||
|
@ -234,6 +242,20 @@ public class MeasureUnitImpl {
|
|||
this.complexity = complexity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the constant denominator.
|
||||
*/
|
||||
public long getConstantDenominator() {
|
||||
return constantDenominator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the constant denominator.
|
||||
*/
|
||||
public void setConstantDenominator(long constantDenominator) {
|
||||
this.constantDenominator = constantDenominator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the MeasureUnitImpl and generates the identifier string in place.
|
||||
*/
|
||||
|
@ -244,7 +266,6 @@ public class MeasureUnitImpl {
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
if (this.complexity == MeasureUnit.Complexity.COMPOUND) {
|
||||
// Note: don't sort a MIXED unit
|
||||
Collections.sort(this.getSingleUnits(), new SingleUnitComparator());
|
||||
|
@ -253,8 +274,7 @@ public class MeasureUnitImpl {
|
|||
StringBuilder result = new StringBuilder();
|
||||
boolean beforePer = true;
|
||||
boolean firstTimeNegativeDimension = false;
|
||||
for (SingleUnitImpl singleUnit :
|
||||
this.getSingleUnits()) {
|
||||
for (SingleUnitImpl singleUnit : this.getSingleUnits()) {
|
||||
if (beforePer && singleUnit.getDimensionality() < 0) {
|
||||
beforePer = false;
|
||||
firstTimeNegativeDimension = true;
|
||||
|
@ -262,6 +282,12 @@ public class MeasureUnitImpl {
|
|||
firstTimeNegativeDimension = false;
|
||||
}
|
||||
|
||||
if (firstTimeNegativeDimension && this.constantDenominator > 0) {
|
||||
result.append("-per-");
|
||||
result.append(this.constantDenominator);
|
||||
firstTimeNegativeDimension = false;
|
||||
}
|
||||
|
||||
if (this.getComplexity() == MeasureUnit.Complexity.MIXED) {
|
||||
if (result.length() != 0) {
|
||||
result.append("-and-");
|
||||
|
@ -283,6 +309,11 @@ public class MeasureUnitImpl {
|
|||
result.append(singleUnit.getNeutralIdentifier());
|
||||
}
|
||||
|
||||
if (this.constantDenominator > 0) {
|
||||
result.append("-per-");
|
||||
result.append(this.constantDenominator);
|
||||
}
|
||||
|
||||
this.identifier = result.toString();
|
||||
}
|
||||
|
||||
|
@ -405,6 +436,28 @@ public class MeasureUnitImpl {
|
|||
}
|
||||
|
||||
public static class UnitsParser {
|
||||
|
||||
/**
|
||||
* Contains a single unit or a constant.
|
||||
*
|
||||
* @throws IllegalArgumentException when both singleUnit and constant are
|
||||
* existing.
|
||||
* @param singleUnit the single unit
|
||||
* @param constant the constant
|
||||
*/
|
||||
private class SingleUnitOrConstant {
|
||||
SingleUnitImpl singleUnit;
|
||||
Long constant;
|
||||
|
||||
SingleUnitOrConstant(SingleUnitImpl singleUnit, Long constant) {
|
||||
if (singleUnit != null && constant != null) {
|
||||
throw new IllegalArgumentException("It is a SingleUnit Or a Constant, not both");
|
||||
}
|
||||
this.singleUnit = singleUnit;
|
||||
this.constant = constant;
|
||||
}
|
||||
}
|
||||
|
||||
// This used only to not build the trie each time we use the parser
|
||||
private volatile static CharsTrie savedTrie = null;
|
||||
|
||||
|
@ -417,14 +470,19 @@ public class MeasureUnitImpl {
|
|||
// are in the denominator. Until we find an "-and-", at which point the
|
||||
// identifier is invalid pending TODO(CLDR-13701).
|
||||
private boolean fAfterPer = false;
|
||||
|
||||
// Set to true when we just parsed a "-per-" or a "per-".
|
||||
// This is used to ensure that the unit constant (such as "per-100-kilometer")
|
||||
// can be parsed when it occurs after a "-per-" or a "per-".
|
||||
private boolean fJustAfterPer = false;
|
||||
|
||||
// If an "-and-" was parsed prior to finding the "single
|
||||
// * unit", sawAnd is set to true. If not, it is left as is.
|
||||
// * 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 static MeasureUnit.MeasurePrefix[] measurePrefixValues = MeasureUnit.MeasurePrefix.values();
|
||||
|
||||
private UnitsParser(String identifier) {
|
||||
this.fSource = identifier;
|
||||
|
@ -514,7 +572,15 @@ public class MeasureUnitImpl {
|
|||
|
||||
while (hasNext()) {
|
||||
fSawAnd = false;
|
||||
SingleUnitImpl singleUnit = nextSingleUnit();
|
||||
SingleUnitOrConstant nextSingleUnitPair = nextSingleUnit();
|
||||
|
||||
if (nextSingleUnitPair.singleUnit == null) {
|
||||
result.setConstantDenominator(nextSingleUnitPair.constant);
|
||||
result.setComplexity(MeasureUnit.Complexity.COMPOUND);
|
||||
continue;
|
||||
}
|
||||
|
||||
SingleUnitImpl singleUnit = nextSingleUnitPair.singleUnit;
|
||||
|
||||
boolean added = result.appendSingleUnit(singleUnit);
|
||||
if (fSawAnd && !added) {
|
||||
|
@ -526,10 +592,11 @@ public class MeasureUnitImpl {
|
|||
// 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;
|
||||
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
|
||||
// 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) {
|
||||
|
@ -538,9 +605,25 @@ public class MeasureUnitImpl {
|
|||
}
|
||||
}
|
||||
|
||||
if (result.getSingleUnits().size() == 0) {
|
||||
throw new IllegalArgumentException("Error in parsing a unit identifier.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Token states definitions.
|
||||
*/
|
||||
enum TokenState {
|
||||
// No tokens seen yet (will accept power, SI or binary prefix, or simple unit)
|
||||
NO_TOKENS_SEEN,
|
||||
// Power token seen (will not accept another power token)
|
||||
POWER_TOKEN_SEEN,
|
||||
// SI or binary prefix token seen (will not accept a power, or SI or binary prefix token)
|
||||
PREFIX_TOKEN_SEEN
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next "single unit" via result.
|
||||
* <p>
|
||||
|
@ -548,27 +631,30 @@ public class MeasureUnitImpl {
|
|||
* dimensionality.
|
||||
* <p>
|
||||
*
|
||||
* @throws IllegalArgumentException if we parse both compound units and "-and-", since mixed
|
||||
* compound units are not yet supported - TODO(CLDR-13701).
|
||||
* @throws IllegalArgumentException if we parse both compound units and "-and-",
|
||||
* since mixed
|
||||
* compound units are not yet supported -
|
||||
* TODO(CLDR-13701).
|
||||
*/
|
||||
private SingleUnitImpl nextSingleUnit() {
|
||||
private SingleUnitOrConstant 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;
|
||||
TokenState state = TokenState.NO_TOKENS_SEEN;
|
||||
|
||||
boolean atStart = fIndex == 0;
|
||||
Token token = nextToken();
|
||||
fJustAfterPer = false;
|
||||
|
||||
if (atStart) {
|
||||
if (token.getType() == Token.Type.TYPE_UNIT_CONSTANT) {
|
||||
throw new IllegalArgumentException("Unit constant cannot be the first token");
|
||||
}
|
||||
// Identifiers optionally start with "per-".
|
||||
if (token.getType() == Token.Type.TYPE_INITIAL_COMPOUND_PART) {
|
||||
assert token.getInitialCompoundPart() == InitialCompoundPart.INITIAL_COMPOUND_PART_PER;
|
||||
|
||||
fAfterPer = true;
|
||||
fJustAfterPer = true;
|
||||
result.setDimensionality(-1);
|
||||
|
||||
token = nextToken();
|
||||
|
@ -589,6 +675,7 @@ public class MeasureUnitImpl {
|
|||
}
|
||||
|
||||
fAfterPer = true;
|
||||
fJustAfterPer = true;
|
||||
result.setDimensionality(-1);
|
||||
break;
|
||||
|
||||
|
@ -610,30 +697,39 @@ public class MeasureUnitImpl {
|
|||
token = nextToken();
|
||||
}
|
||||
|
||||
// Treat unit constant
|
||||
if (token.getType() == Token.Type.TYPE_UNIT_CONSTANT) {
|
||||
if (!fJustAfterPer) {
|
||||
throw new IllegalArgumentException("Unit constant cannot be the first token");
|
||||
}
|
||||
|
||||
return new SingleUnitOrConstant(null, token.getConstantDenominator());
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (state != TokenState.NO_TOKENS_SEEN) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
result.setDimensionality(result.getDimensionality() * token.getPower());
|
||||
state = 1;
|
||||
state = TokenState.POWER_TOKEN_SEEN;
|
||||
break;
|
||||
|
||||
case TYPE_PREFIX:
|
||||
if (state > 1) {
|
||||
if (state == TokenState.PREFIX_TOKEN_SEEN) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
result.setPrefix(token.getPrefix());
|
||||
state = 2;
|
||||
state = TokenState.PREFIX_TOKEN_SEEN;
|
||||
break;
|
||||
|
||||
case TYPE_SIMPLE_UNIT:
|
||||
result.setSimpleUnit(token.getSimpleUnitIndex(), UnitsData.getSimpleUnits());
|
||||
return result;
|
||||
return new SingleUnitOrConstant(result, null);
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
|
@ -653,95 +749,140 @@ public class MeasureUnitImpl {
|
|||
|
||||
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;
|
||||
int matchingValue = -1;
|
||||
// Saves the position in the `fSource` string at the end of the most
|
||||
// recently matched token.
|
||||
int prevIndex = -1;
|
||||
|
||||
int savedIndex = fIndex;
|
||||
|
||||
// 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) {
|
||||
}
|
||||
|
||||
if (result == BytesTrie.Result.NO_VALUE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match = trie.getValue();
|
||||
previ = fIndex;
|
||||
matchingValue = trie.getValue();
|
||||
prevIndex = fIndex;
|
||||
|
||||
if (result == BytesTrie.Result.FINAL_VALUE) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (result != BytesTrie.Result.INTERMEDIATE_VALUE) {
|
||||
throw new IllegalArgumentException("result must has an intermediate value");
|
||||
throw new IllegalArgumentException("Result must have an intermediate value");
|
||||
}
|
||||
|
||||
// continue;
|
||||
}
|
||||
|
||||
if (matchingValue < 0) {
|
||||
if (fJustAfterPer) {
|
||||
// We've just parsed a "per-", so we can expect a unit constant.
|
||||
int hyphenIndex = fSource.indexOf('-', savedIndex);
|
||||
|
||||
if (match < 0) {
|
||||
throw new IllegalArgumentException("Encountered unknown token starting at index " + previ);
|
||||
// extract the unit constant from the string
|
||||
String unitConstant = (hyphenIndex == -1) ? fSource.substring(savedIndex)
|
||||
: fSource.substring(savedIndex, hyphenIndex);
|
||||
fIndex = (hyphenIndex == -1) ? fSource.length() : hyphenIndex;
|
||||
|
||||
return Token.tokenWithConstant(unitConstant);
|
||||
|
||||
} else {
|
||||
throw new IllegalArgumentException("Encountered unknown token starting at index " + prevIndex);
|
||||
}
|
||||
} else {
|
||||
fIndex = previ;
|
||||
fIndex = prevIndex;
|
||||
}
|
||||
|
||||
return new Token(match);
|
||||
return new Token(matchingValue);
|
||||
}
|
||||
|
||||
static class Token {
|
||||
|
||||
private final int fMatch;
|
||||
private final long fMatch;
|
||||
private final Type type;
|
||||
|
||||
public Token(int fMatch) {
|
||||
public Token(long fMatch) {
|
||||
this.fMatch = fMatch;
|
||||
type = calculateType(fMatch);
|
||||
}
|
||||
|
||||
private Token(long fMatch, Type type) {
|
||||
this.fMatch = fMatch;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public static Token tokenWithConstant(String constantStr) {
|
||||
BigDecimal unitConstantValue = new BigDecimal(constantStr);
|
||||
if (unitConstantValue.scale() <= 0 && unitConstantValue.compareTo(BigDecimal.ZERO) >= 0
|
||||
&& unitConstantValue.compareTo(BigDecimal.valueOf(Long.MAX_VALUE)) <= 0) {
|
||||
return new Token(unitConstantValue.longValueExact(), Type.TYPE_UNIT_CONSTANT);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"The unit constant value is not a valid non-negative long integer.");
|
||||
}
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public MeasureUnit.MeasurePrefix getPrefix() {
|
||||
assert this.type == Type.TYPE_PREFIX;
|
||||
return getPrefixFromTrieIndex(this.fMatch);
|
||||
assert this.fMatch <= Integer.MAX_VALUE;
|
||||
|
||||
int trieIndex = (int) this.fMatch;
|
||||
return getPrefixFromTrieIndex(trieIndex);
|
||||
}
|
||||
|
||||
// Valid only for tokens with type TYPE_UNIT_CONSTANT.
|
||||
public long getConstantDenominator() {
|
||||
assert this.type == Type.TYPE_UNIT_CONSTANT;
|
||||
return this.fMatch;
|
||||
}
|
||||
|
||||
// Valid only for tokens with type TYPE_COMPOUND_PART.
|
||||
public int getMatch() {
|
||||
assert getType() == Type.TYPE_COMPOUND_PART;
|
||||
return fMatch;
|
||||
assert this.fMatch <= Integer.MAX_VALUE;
|
||||
|
||||
int matchIndex = (int) this.fMatch;
|
||||
return matchIndex;
|
||||
}
|
||||
|
||||
// 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);
|
||||
assert this.type == Type.TYPE_INITIAL_COMPOUND_PART;
|
||||
assert fMatch == InitialCompoundPart.INITIAL_COMPOUND_PART_PER.getTrieIndex();
|
||||
assert fMatch <= Integer.MAX_VALUE;
|
||||
int trieIndex = (int) fMatch;
|
||||
return InitialCompoundPart.getInitialCompoundPartFromTrieIndex(trieIndex);
|
||||
}
|
||||
|
||||
public int getPower() {
|
||||
assert this.type == Type.TYPE_POWER_PART;
|
||||
return PowerPart.getPowerFromTrieIndex(this.fMatch);
|
||||
assert this.fMatch <= Integer.MAX_VALUE;
|
||||
int trieIndex = (int) this.fMatch;
|
||||
return PowerPart.getPowerFromTrieIndex(trieIndex);
|
||||
}
|
||||
|
||||
public int getSimpleUnitIndex() {
|
||||
assert this.type == Type.TYPE_SIMPLE_UNIT;
|
||||
return this.fMatch - UnitsData.Constants.kSimpleUnitOffset;
|
||||
assert this.fMatch <= Integer.MAX_VALUE;
|
||||
return ((int) 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) {
|
||||
// It is invalid to call calculateType() with a non-positive Token value,
|
||||
// as it will result in an assertion failure.
|
||||
private Type calculateType(long fMatch) {
|
||||
if (fMatch <= 0) {
|
||||
throw new AssertionError("fMatch must have a positive value");
|
||||
}
|
||||
|
||||
if (fMatch < UnitsData.Constants.kCompoundPartOffset) {
|
||||
return Type.TYPE_PREFIX;
|
||||
}
|
||||
|
@ -767,6 +908,7 @@ public class MeasureUnitImpl {
|
|||
TYPE_INITIAL_COMPOUND_PART,
|
||||
TYPE_POWER_PART,
|
||||
TYPE_SIMPLE_UNIT,
|
||||
TYPE_UNIT_CONSTANT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,14 +44,15 @@ public class MeasureUnit implements Serializable {
|
|||
private static final long serialVersionUID = -1839973855554750484L;
|
||||
|
||||
// Cache of MeasureUnits.
|
||||
// All access to the cache or cacheIsPopulated flag must be synchronized on class MeasureUnit,
|
||||
// All access to the cache or cacheIsPopulated flag must be synchronized on
|
||||
// class MeasureUnit,
|
||||
// i.e. from synchronized static methods. Beware of non-static methods.
|
||||
private static final Map<String, Map<String,MeasureUnit>> cache
|
||||
= new HashMap<>();
|
||||
private static final Map<String, Map<String, MeasureUnit>> cache = new HashMap<>();
|
||||
private static boolean cacheIsPopulated = false;
|
||||
|
||||
/**
|
||||
* If type set to null, measureUnitImpl is in use instead of type and subType.
|
||||
*
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
|
@ -59,7 +60,9 @@ public class MeasureUnit implements Serializable {
|
|||
protected final String type;
|
||||
|
||||
/**
|
||||
* If subType set to null, measureUnitImpl is in use instead of type and subType.
|
||||
* If subType set to null, measureUnitImpl is in use instead of type and
|
||||
* subType.
|
||||
*
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
|
@ -76,14 +79,18 @@ public class MeasureUnit implements Serializable {
|
|||
/**
|
||||
* Enumeration for unit complexity. There are three levels:
|
||||
* <ul>
|
||||
* <li>SINGLE: A single unit, optionally with a power and/or SI or binary prefix.
|
||||
* <li>SINGLE: A single unit, optionally with a power and/or SI or binary
|
||||
* prefix.
|
||||
* Examples: hectare, square-kilometer, kilojoule, per-second, mebibyte.</li>
|
||||
* <li>COMPOUND: A unit composed of the product of multiple single units. Examples:
|
||||
* <li>COMPOUND: A unit composed of the product of multiple single units.
|
||||
* Examples:
|
||||
* meter-per-second, kilowatt-hour, kilogram-meter-per-square-second.</li>
|
||||
* <li>MIXED: A unit composed of the sum of multiple single units. Examples: foot-and-inch,
|
||||
* <li>MIXED: A unit composed of the sum of multiple single units. Examples:
|
||||
* foot-and-inch,
|
||||
* hour-and-minute-and-second, degree-and-arcminute-and-arcsecond.</li>
|
||||
* </ul>
|
||||
* The complexity determines which operations are available. For example, you cannot set the power
|
||||
* The complexity determines which operations are available. For example, you
|
||||
* cannot set the power
|
||||
* or prefix of a compound unit.
|
||||
*
|
||||
* @stable ICU 68
|
||||
|
@ -448,8 +455,6 @@ public class MeasureUnit implements Serializable {
|
|||
this.measureUnitImpl = measureUnitImpl.copy();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the type, such as "length". May return null.
|
||||
*
|
||||
|
@ -459,7 +464,6 @@ public class MeasureUnit implements Serializable {
|
|||
return type;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the subType, such as “foot”. May return null.
|
||||
*
|
||||
|
@ -495,18 +499,21 @@ public class MeasureUnit implements Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates a MeasureUnit which is this SINGLE unit augmented with the specified prefix.
|
||||
* Creates a MeasureUnit which is this SINGLE unit augmented with the specified
|
||||
* prefix.
|
||||
* For example, MeasurePrefix.KILO for "kilo", or MeasurePrefix.KIBI for "kibi".
|
||||
* May return {@code this} if this unit already has that prefix.
|
||||
* <p>
|
||||
* There is sufficient locale data to format all standard prefixes.
|
||||
* <p>
|
||||
* NOTE: Only works on SINGLE units. If this is a COMPOUND or MIXED unit, an error will
|
||||
* NOTE: Only works on SINGLE units. If this is a COMPOUND or MIXED unit, an
|
||||
* error will
|
||||
* occur. For more information, {@link Complexity}.
|
||||
*
|
||||
* @param prefix The prefix, from MeasurePrefix.
|
||||
* @return A new SINGLE unit.
|
||||
* @throws UnsupportedOperationException if this unit is a COMPOUND or MIXED unit.
|
||||
* @throws UnsupportedOperationException if this unit is a COMPOUND or MIXED
|
||||
* unit.
|
||||
* @stable ICU 69
|
||||
*/
|
||||
public MeasureUnit withPrefix(MeasurePrefix prefix) {
|
||||
|
@ -531,10 +538,80 @@ public class MeasureUnit implements Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the dimensionality (power) of this MeasureUnit. For example, if the unit is square,
|
||||
* Creates a new MeasureUnit with a specified constant denominator.
|
||||
* <p>
|
||||
* This method is applicable only to COMPOUND & SINGLE units. If invoked on a
|
||||
* MIXED unit, an exception will be thrown.
|
||||
* For further details, refer to {@link Complexity}.
|
||||
* <p>
|
||||
*
|
||||
* NOTE: If the constant denominator is set to 0, it means that you are removing
|
||||
* the constant denominator.
|
||||
*
|
||||
*
|
||||
* @param denominator The constant denominator to set.
|
||||
* @return A new MeasureUnit with the specified constant denominator.
|
||||
* @throws UnsupportedOperationException if the unit is not a COMPOUND unit.
|
||||
* @draft ICU 77
|
||||
*/
|
||||
public MeasureUnit withConstantDenominator(long denominator) {
|
||||
if (this.getComplexity() != Complexity.COMPOUND && this.getComplexity() != Complexity.SINGLE) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Constant denominator can only be applied to COMPOUND & SINGLE units");
|
||||
}
|
||||
|
||||
MeasureUnitImpl measureUnitImpl = getCopyOfMeasureUnitImpl();
|
||||
measureUnitImpl.setConstantDenominator(denominator);
|
||||
|
||||
measureUnitImpl.setComplexity(denominator == 0 && measureUnitImpl.getSingleUnits().size() == 1
|
||||
? Complexity.SINGLE
|
||||
: Complexity.COMPOUND);
|
||||
|
||||
return measureUnitImpl.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the constant denominator for this COMPOUND unit.
|
||||
* <p>
|
||||
* Examples:
|
||||
* <ul>
|
||||
* <li>For the unit "liter-per-1000-kiloliter", the constant denominator is
|
||||
* 1000.</li>
|
||||
* <li>For the unit "liter-per-kilometer", the constant denominator is
|
||||
* zero.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* This method is applicable only to COMPOUND & SINGLE units. If invoked on a
|
||||
* MIXED unit, an exception will be thrown.
|
||||
* For further details, refer to {@link Complexity}.
|
||||
* <p>
|
||||
*
|
||||
* NOTE: If no constant denominator exists, the method returns 0.
|
||||
*
|
||||
* @return The value of the constant denominator.
|
||||
* @throws UnsupportedOperationException if the unit is not a COMPOUND unit.
|
||||
* @draft ICU 77
|
||||
*/
|
||||
public long getConstantDenominator() {
|
||||
if (this.getComplexity() != Complexity.COMPOUND && this.getComplexity() != Complexity.SINGLE) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Constant denominator is only supported for COMPOUND & SINGLE units");
|
||||
}
|
||||
|
||||
if (this.measureUnitImpl == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.measureUnitImpl.getConstantDenominator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the dimensionality (power) of this MeasureUnit. For example, if the
|
||||
* unit is square,
|
||||
* then 2 is returned.
|
||||
* <p>
|
||||
* NOTE: Only works on SINGLE units. If this is a COMPOUND or MIXED unit, an exception will be thrown.
|
||||
* NOTE: Only works on SINGLE units. If this is a COMPOUND or MIXED unit, an
|
||||
* exception will be thrown.
|
||||
* For more information, {@link Complexity}.
|
||||
*
|
||||
* @return The dimensionality (power) of this simple unit.
|
||||
|
@ -546,10 +623,12 @@ public class MeasureUnit implements Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates a MeasureUnit which is this SINGLE unit augmented with the specified dimensionality
|
||||
* Creates a MeasureUnit which is this SINGLE unit augmented with the specified
|
||||
* dimensionality
|
||||
* (power). For example, if dimensionality is 2, the unit will be squared.
|
||||
* <p>
|
||||
* NOTE: Only works on SINGLE units. If this is a COMPOUND or MIXED unit, an exception is thrown.
|
||||
* NOTE: Only works on SINGLE units. If this is a COMPOUND or MIXED unit, an
|
||||
* exception is thrown.
|
||||
* For more information, {@link Complexity}.
|
||||
*
|
||||
* @param dimensionality The dimensionality (power).
|
||||
|
@ -564,33 +643,47 @@ public class MeasureUnit implements Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Computes the reciprocal of this MeasureUnit, with the numerator and denominator flipped.
|
||||
* Computes the reciprocal of this MeasureUnit, with the numerator and
|
||||
* denominator flipped.
|
||||
* <p>
|
||||
* For example, if the receiver is "meter-per-second", the unit "second-per-meter" is returned.
|
||||
* For example, if the receiver is "meter-per-second", the unit
|
||||
* "second-per-meter" is returned.
|
||||
* <p>
|
||||
* NOTE: Only works on SINGLE and COMPOUND units. If this is a MIXED unit, an error will
|
||||
* NOTE: Only works on SINGLE and COMPOUND units. If this is a MIXED unit, an
|
||||
* error will
|
||||
* occur. For more information, {@link Complexity}.
|
||||
*
|
||||
* <p>
|
||||
* NOTE: An exception will be thrown for units that have a constant denominator.
|
||||
*
|
||||
* @return The reciprocal of the target unit.
|
||||
* @throws UnsupportedOperationException if the unit is MIXED.
|
||||
* @throws UnsupportedOperationException if the unit is MIXED or has a constant
|
||||
* denominator.
|
||||
* @stable ICU 68
|
||||
*/
|
||||
public MeasureUnit reciprocal() {
|
||||
if (this.getComplexity() == Complexity.COMPOUND && this.getConstantDenominator() != 0) {
|
||||
throw new UnsupportedOperationException("Cannot take reciprocal of a unit with a constant denominator");
|
||||
}
|
||||
|
||||
MeasureUnitImpl measureUnit = getCopyOfMeasureUnitImpl();
|
||||
measureUnit.takeReciprocal();
|
||||
return measureUnit.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the product of this unit with another unit. This is a way to build units from
|
||||
* Computes the product of this unit with another unit. This is a way to build
|
||||
* units from
|
||||
* constituent parts.
|
||||
* <p>
|
||||
* The numerator and denominator are preserved through this operation.
|
||||
* <p>
|
||||
* For example, if the receiver is "kilowatt" and the argument is "hour-per-day", then the
|
||||
* For example, if the receiver is "kilowatt" and the argument is
|
||||
* "hour-per-day", then the
|
||||
* unit "kilowatt-hour-per-day" is returned.
|
||||
* <p>
|
||||
* NOTE: Only works on SINGLE and COMPOUND units. If either unit (receivee and argument) is a
|
||||
* NOTE: Only works on SINGLE and COMPOUND units. If either unit (receivee and
|
||||
* argument) is a
|
||||
* MIXED unit, an error will occur. For more information, {@link Complexity}.
|
||||
*
|
||||
* @param other The MeasureUnit to multiply with the target.
|
||||
|
@ -610,8 +703,7 @@ public class MeasureUnit implements Serializable {
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
for (SingleUnitImpl singleUnit :
|
||||
otherImplRef.getSingleUnits()) {
|
||||
for (SingleUnitImpl singleUnit : otherImplRef.getSingleUnits()) {
|
||||
implCopy.appendSingleUnit(singleUnit);
|
||||
}
|
||||
|
||||
|
@ -619,7 +711,8 @@ public class MeasureUnit implements Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the list of SINGLE units contained within a sequence of COMPOUND units.
|
||||
* Returns the list of SINGLE units contained within a sequence of COMPOUND
|
||||
* units.
|
||||
* <p>
|
||||
* Examples:
|
||||
* - Given "meter-kilogram-per-second", three units will be returned: "meter",
|
||||
|
@ -628,13 +721,18 @@ public class MeasureUnit implements Serializable {
|
|||
* and "second".
|
||||
* <p>
|
||||
* If this is a SINGLE unit, a list of length 1 will be returned.
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* NOTE: For units with a constant denominator, the returned single units will
|
||||
* not include the constant denominator.
|
||||
* To obtain the constant denominator, retrieve it from the original unit.
|
||||
* <p>
|
||||
*
|
||||
* @return An unmodifiable list of single units
|
||||
* @stable ICU 68
|
||||
*/
|
||||
public List<MeasureUnit> splitToSingleUnits() {
|
||||
final ArrayList<SingleUnitImpl> singleUnits =
|
||||
getMaybeReferenceOfMeasureUnitImpl().getSingleUnits();
|
||||
final ArrayList<SingleUnitImpl> singleUnits = getMaybeReferenceOfMeasureUnitImpl().getSingleUnits();
|
||||
List<MeasureUnit> result = new ArrayList<>(singleUnits.size());
|
||||
for (SingleUnitImpl singleUnit : singleUnits) {
|
||||
result.add(singleUnit.build());
|
||||
|
@ -693,6 +791,7 @@ public class MeasureUnit implements Serializable {
|
|||
|
||||
/**
|
||||
* For the given type, return the available units.
|
||||
*
|
||||
* @param type the type
|
||||
* @return the available units for type. Returned set is unmodifiable.
|
||||
* @stable ICU 53
|
||||
|
@ -725,9 +824,11 @@ public class MeasureUnit implements Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates a MeasureUnit instance (creates a singleton instance) or returns one from the cache.
|
||||
* Creates a MeasureUnit instance (creates a singleton instance) or returns one
|
||||
* from the cache.
|
||||
* <p>
|
||||
* Normally this method should not be used, since there will be no formatting data
|
||||
* Normally this method should not be used, since there will be no formatting
|
||||
* data
|
||||
* available for it, and it may not be returned by getAvailable().
|
||||
* However, for special purposes (such as CLDR tooling), it is available.
|
||||
*
|
||||
|
@ -804,7 +905,7 @@ public class MeasureUnit implements Serializable {
|
|||
static Factory TIMEUNIT_FACTORY = new Factory() {
|
||||
@Override
|
||||
public MeasureUnit create(String type, String subType) {
|
||||
return new TimeUnit(type, subType);
|
||||
return new TimeUnit(type, subType);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -816,7 +917,8 @@ public class MeasureUnit implements Serializable {
|
|||
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
|
||||
UResource.Table unitTypesTable = value.getTable();
|
||||
for (int i2 = 0; unitTypesTable.getKeyAndValue(i2, key, value); ++i2) {
|
||||
// Skip "compound" and "coordinate" since they are treated differently from the other units
|
||||
// Skip "compound" and "coordinate" since they are treated differently from the
|
||||
// other units
|
||||
if (key.contentEquals("compound") || key.contentEquals("coordinate")) {
|
||||
continue;
|
||||
}
|
||||
|
@ -849,7 +951,8 @@ public class MeasureUnit implements Serializable {
|
|||
* Population is done lazily, in response to MeasureUnit.getAvailable()
|
||||
* or other API that expects to see all of the MeasureUnits.
|
||||
*
|
||||
* <p>At static initialization time the MeasureUnits cache is populated
|
||||
* <p>
|
||||
* At static initialization time the MeasureUnits cache is populated
|
||||
* with public static instances (G_FORCE, METER_PER_SECOND_SQUARED, etc.) only.
|
||||
* Adding of others is deferred until later to avoid circular static init
|
||||
* dependencies with classes Currency and TimeUnit.
|
||||
|
|
Loading…
Add table
Reference in a new issue