mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-08 06:53:45 +00:00
parent
a8a699b88e
commit
d149031433
5 changed files with 1911 additions and 38 deletions
|
@ -0,0 +1,737 @@
|
|||
// © 2020 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html
|
||||
|
||||
|
||||
package com.ibm.icu.impl.units;
|
||||
|
||||
import com.ibm.icu.util.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
|
||||
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 simple 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 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 <code>IllegalArgumentException</code> 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 clone() {
|
||||
MeasureUnitImpl result = new MeasureUnitImpl();
|
||||
result.complexity = this.complexity;
|
||||
result.identifier = this.identifier;
|
||||
result.singleUnits = (ArrayList<SingleUnitImpl>) this.singleUnits.clone();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the list of all the individual units inside the `MeasureUnitImpl`.
|
||||
* For example:
|
||||
* - if the <code>MeasureUnitImpl</code> is <code>foot-per-hour</code>
|
||||
* it will return a list of 1 <code>{foot-per-hour}</code>
|
||||
* - if the <code>MeasureUnitImpl</code> is <code>foot-and-inch</code>
|
||||
* it will return a list of 2 <code>{ foot, inch}</code>
|
||||
*
|
||||
* @return a list of <code>MeasureUnitImpl</code>
|
||||
*/
|
||||
public ArrayList<MeasureUnitImpl> extractIndividualUnits() {
|
||||
ArrayList<MeasureUnitImpl> result = new ArrayList<MeasureUnitImpl>();
|
||||
if (this.getComplexity() == MeasureUnit.Complexity.MIXED) {
|
||||
// In case of mixed units, each single unit can be considered as a stand alone MeasureUnitImpl.
|
||||
for (SingleUnitImpl singleUnit :
|
||||
this.getSingleUnits()) {
|
||||
result.add(new MeasureUnitImpl(singleUnit));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
result.add(this.clone());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
// We don't append dimensionless units.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find a similar unit that already exists, to attempt to coalesce
|
||||
SingleUnitImpl oldUnit = null;
|
||||
for (int i = 0, n = this.singleUnits.size(); i < n; i++) {
|
||||
SingleUnitImpl candidate = this.singleUnits.get(i);
|
||||
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.clone());
|
||||
|
||||
// If the MeasureUnitImpl is `UMEASURE_UNIT_SINGLE` and after the appending a unit, the singleUnits are more
|
||||
// than one singleUnit. 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).clone();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
String singleUnitIdentifier = singleUnit.getNeutralIdentifier();
|
||||
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(singleUnitIdentifier);
|
||||
}
|
||||
|
||||
this.identifier = result.toString();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class UnitsParser {
|
||||
// This used only to not build the trie each time we use the parser
|
||||
private volatile static CharsTrie savedTrie = null;
|
||||
private final String[] simpleUnits;
|
||||
// This trie used in the parsing operation.
|
||||
private CharsTrie trie;
|
||||
// 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-13700).
|
||||
private boolean fAfterPer = false;
|
||||
private String fSource;
|
||||
// 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;
|
||||
|
||||
private UnitsParser(String identifier) {
|
||||
this.simpleUnits = UnitsData.getSimpleUnits();
|
||||
this.fSource = identifier;
|
||||
|
||||
if (UnitsParser.savedTrie != null) {
|
||||
try {
|
||||
this.trie = UnitsParser.savedTrie.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new ICUCloneNotSupportedException();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Building the trie.
|
||||
CharsTrieBuilder trieBuilder;
|
||||
trieBuilder = new CharsTrieBuilder();
|
||||
|
||||
// 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 SI prefixes
|
||||
for (MeasureUnit.SIPrefix siPrefix :
|
||||
MeasureUnit.SIPrefix.values()) {
|
||||
trieBuilder.add(siPrefix.getIdentifier(), getTrieIndex(siPrefix));
|
||||
}
|
||||
|
||||
// Add simple units
|
||||
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);
|
||||
|
||||
try {
|
||||
this.trie = UnitsParser.savedTrie.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new ICUCloneNotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.SIPrefix getSiPrefixFromTrieIndex(int trieIndex) {
|
||||
for (MeasureUnit.SIPrefix element :
|
||||
MeasureUnit.SIPrefix.values()) {
|
||||
if (getTrieIndex(element) == trieIndex)
|
||||
return element;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Incorrect trieIndex");
|
||||
}
|
||||
|
||||
private static int getTrieIndex(MeasureUnit.SIPrefix prefix) {
|
||||
return prefix.getSiPrefixPower() + UnitsData.Constants.kSIPrefixOffset;
|
||||
}
|
||||
|
||||
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 `UMEASURE_UNIT_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-13700).
|
||||
*/
|
||||
private SingleUnitImpl nextSingleUnit() {
|
||||
SingleUnitImpl result = new SingleUnitImpl();
|
||||
|
||||
// state:
|
||||
// 0 = no tokens seen yet (will accept power, SI prefix, or simple unit)
|
||||
// 1 = power token seen (will not accept another power token)
|
||||
// 2 = SI prefix token seen (will not accept a power or SI 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-13700).
|
||||
}
|
||||
|
||||
fAfterPer = true;
|
||||
result.setDimensionality(-1);
|
||||
break;
|
||||
|
||||
case TIMES:
|
||||
if (fAfterPer) {
|
||||
result.setDimensionality(-1);
|
||||
}
|
||||
break;
|
||||
|
||||
case AND:
|
||||
if (fAfterPer) {
|
||||
// not yet supported, TODO(CLDR-13700).
|
||||
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_SI_PREFIX:
|
||||
if (state > 1) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
result.setSiPrefix(token.getSIPrefix());
|
||||
state = 2;
|
||||
break;
|
||||
|
||||
case TYPE_SIMPLE_UNIT:
|
||||
result.setSimpleUnit(token.getSimpleUnitIndex(), simpleUnits);
|
||||
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.SIPrefix getSIPrefix() {
|
||||
assert this.type == Type.TYPE_SI_PREFIX;
|
||||
return getSiPrefixFromTrieIndex(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() {
|
||||
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_SI_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_SI_PREFIX,
|
||||
// Token type for "-per-", "-", and "-and-".
|
||||
TYPE_COMPOUND_PART,
|
||||
// Token type for "per-".
|
||||
TYPE_INITIAL_COMPOUND_PART,
|
||||
TYPE_POWER_PART,
|
||||
TYPE_SIMPLE_UNIT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SingleUnitComparator implements Comparator<SingleUnitImpl> {
|
||||
@Override
|
||||
public int compare(SingleUnitImpl o1, SingleUnitImpl o2) {
|
||||
return o1.compareTo(o2);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
// © 2020 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html
|
||||
|
||||
|
||||
package com.ibm.icu.impl.units;
|
||||
|
||||
import com.ibm.icu.util.MeasureUnit;
|
||||
|
||||
public class SingleUnitImpl {
|
||||
/**
|
||||
* Simple unit index, unique for every simple unit, -1 for the dimensionless
|
||||
* unit. This is an index into a string list in unit.txt {ConversionUnits}.
|
||||
* <p>
|
||||
* The default value is -1, meaning the dimensionless unit:
|
||||
* isDimensionless() will return true, until index is changed.
|
||||
*/
|
||||
private int index = -1;
|
||||
/**
|
||||
* SimpleUnit is the simplest form of a Unit. For example, for "square-millimeter", the simple unit would be "meter"Ò
|
||||
* <p>
|
||||
* The default value is "", meaning the dimensionless unit:
|
||||
* isDimensionless() will return true, until index is changed.
|
||||
*/
|
||||
private String simpleUnit = "";
|
||||
/**
|
||||
* Determine the power of the `SingleUnit`. For example, for "square-meter", the dimensionality will be `2`.
|
||||
* <p>
|
||||
* NOTE:
|
||||
* Default dimensionality is 1.
|
||||
*/
|
||||
private int dimensionality = 1;
|
||||
/**
|
||||
* SI Prefix
|
||||
*/
|
||||
private MeasureUnit.SIPrefix siPrefix = MeasureUnit.SIPrefix.ONE;
|
||||
|
||||
public SingleUnitImpl clone() {
|
||||
SingleUnitImpl result = new SingleUnitImpl();
|
||||
result.index = this.index;
|
||||
result.dimensionality = this.dimensionality;
|
||||
result.simpleUnit = this.simpleUnit;
|
||||
result.siPrefix = this.siPrefix;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public MeasureUnit build() {
|
||||
MeasureUnitImpl measureUnit = new MeasureUnitImpl(this);
|
||||
return measureUnit.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an neutral identifier string for a single unit which means we do not include the dimension signal.
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
*/
|
||||
public String getNeutralIdentifier() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
int posPower = Math.abs(this.getDimensionality());
|
||||
|
||||
assert posPower > 0 : "getIdentifier does not support the dimensionless";
|
||||
|
||||
if (posPower == 1) {
|
||||
// no-op
|
||||
} else if (posPower == 2) {
|
||||
result.append("square-");
|
||||
} else if (posPower == 3) {
|
||||
result.append("cubic-");
|
||||
} else if (posPower <= 15) {
|
||||
result.append("pow");
|
||||
result.append(posPower);
|
||||
result.append('-');
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unit Identifier Syntax Error");
|
||||
}
|
||||
|
||||
result.append(this.getSiPrefix().getIdentifier());
|
||||
result.append(this.getSimpleUnit());
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare this SingleUnitImpl to another SingleUnitImpl for the sake of
|
||||
* sorting and coalescing.
|
||||
* <p>
|
||||
* Takes the sign of dimensionality into account, but not the absolute
|
||||
* value: per-meter is not considered the same as meter, but meter is
|
||||
* considered the same as square-meter.
|
||||
* <p>
|
||||
* The dimensionless unit generally does not get compared, but if it did, it
|
||||
* would sort before other units by virtue of index being < 0 and
|
||||
* dimensionality not being negative.
|
||||
*/
|
||||
int compareTo(SingleUnitImpl other) {
|
||||
if (dimensionality < 0 && other.dimensionality > 0) {
|
||||
// Positive dimensions first
|
||||
return 1;
|
||||
}
|
||||
if (dimensionality > 0 && other.dimensionality < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (index < other.index) {
|
||||
return -1;
|
||||
}
|
||||
if (index > other.index) {
|
||||
return 1;
|
||||
}
|
||||
if (this.getSiPrefix().getSiPrefixPower() < other.getSiPrefix().getSiPrefixPower()) {
|
||||
return -1;
|
||||
}
|
||||
if (this.getSiPrefix().getSiPrefixPower() > other.getSiPrefix().getSiPrefixPower()) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this SingleUnitImpl is compatible with another for the purpose of coalescing.
|
||||
* <p>
|
||||
* Units with the same base unit and SI prefix should match, except that they must also have
|
||||
* the same dimensionality sign, such that we don't merge numerator and denominator.
|
||||
*/
|
||||
boolean isCompatibleWith(SingleUnitImpl other) {
|
||||
return (compareTo(other) == 0);
|
||||
}
|
||||
|
||||
public String getSimpleUnit() {
|
||||
return simpleUnit;
|
||||
}
|
||||
|
||||
public void setSimpleUnit(int simpleUnitIndex, String[] simpleUnits) {
|
||||
this.index = simpleUnitIndex;
|
||||
this.simpleUnit = simpleUnits[simpleUnitIndex];
|
||||
}
|
||||
|
||||
public int getDimensionality() {
|
||||
return dimensionality;
|
||||
}
|
||||
|
||||
public void setDimensionality(int dimensionality) {
|
||||
this.dimensionality = dimensionality;
|
||||
}
|
||||
|
||||
public MeasureUnit.SIPrefix getSiPrefix() {
|
||||
return siPrefix;
|
||||
}
|
||||
|
||||
public void setSiPrefix(MeasureUnit.SIPrefix siPrefix) {
|
||||
this.siPrefix = siPrefix;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
// © 2020 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html
|
||||
|
||||
|
||||
package com.ibm.icu.impl.units;
|
||||
|
||||
import com.ibm.icu.impl.ICUData;
|
||||
import com.ibm.icu.impl.ICUResourceBundle;
|
||||
import com.ibm.icu.impl.UResource;
|
||||
import com.ibm.icu.util.UResourceBundle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Responsible for all units data operations (retriever, analysis, extraction certain data ... etc.).
|
||||
*/
|
||||
class UnitsData {
|
||||
private volatile static String[] simpleUnits = null;
|
||||
|
||||
public static String[] getSimpleUnits() {
|
||||
if (simpleUnits != null) {
|
||||
return simpleUnits;
|
||||
}
|
||||
|
||||
// Read simple units
|
||||
ICUResourceBundle resource;
|
||||
resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, "units");
|
||||
SimpleUnitIdentifiersSink sink = new SimpleUnitIdentifiersSink();
|
||||
resource.getAllItemsWithFallback("convertUnits", sink);
|
||||
simpleUnits = sink.simpleUnits;
|
||||
|
||||
return simpleUnits;
|
||||
}
|
||||
|
||||
public static class SimpleUnitIdentifiersSink extends UResource.Sink {
|
||||
String[] simpleUnits = null;
|
||||
|
||||
@Override
|
||||
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
|
||||
assert key.toString().equals(Constants.CONVERSION_UNIT_TABLE_NAME);
|
||||
assert value.getType() == UResourceBundle.TABLE;
|
||||
|
||||
UResource.Table simpleUnitsTable = value.getTable();
|
||||
ArrayList<String> simpleUnits = new ArrayList<>();
|
||||
for (int i = 0; simpleUnitsTable.getKeyAndValue(i, key, value); i++) {
|
||||
if (key.toString().equals("kilogram")) {
|
||||
|
||||
// For parsing, we use "gram", the prefixless metric mass unit. We
|
||||
// thus ignore the SI Base Unit of Mass: it exists due to being the
|
||||
// mass conversion target unit, but not needed for MeasureUnit
|
||||
// parsing.
|
||||
continue;
|
||||
}
|
||||
|
||||
simpleUnits.add(key.toString());
|
||||
}
|
||||
|
||||
this.simpleUnits = simpleUnits.toArray(new String[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains all the needed constants.
|
||||
*/
|
||||
public static class Constants {
|
||||
// Trie value offset for simple units, e.g. "gram", "nautical-mile",
|
||||
// "fluid-ounce-imperial".
|
||||
public static final int kSimpleUnitOffset = 512;
|
||||
|
||||
// Trie value offset for powers like "square-", "cubic-", "pow2-" etc.
|
||||
public static final int kPowerPartOffset = 256;
|
||||
|
||||
|
||||
// Trie value offset for "per-".
|
||||
public final static int kInitialCompoundPartOffset = 192;
|
||||
|
||||
// Trie value offset for compound parts, e.g. "-per-", "-", "-and-".
|
||||
public final static int kCompoundPartOffset = 128;
|
||||
|
||||
// Trie value offset for SI Prefixes. This is big enough to ensure we only
|
||||
// insert positive integers into the trie.
|
||||
public static final int kSIPrefixOffset = 64;
|
||||
|
||||
|
||||
/* Tables Names*/
|
||||
public static final String CONVERSION_UNIT_TABLE_NAME = "convertUnits";
|
||||
public static final String UNIT_PREFERENCE_TABLE_NAME = "unitPreferenceData";
|
||||
public static final String CATEGORY_TABLE_NAME = "unitQuantities";
|
||||
public static final String DEFAULT_REGION = "001";
|
||||
public static final String DEFAULT_USAGE = "default";
|
||||
}
|
||||
}
|
|
@ -14,10 +14,12 @@ import java.io.ObjectInput;
|
|||
import java.io.ObjectOutput;
|
||||
import java.io.ObjectStreamException;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.ibm.icu.impl.CollectionSet;
|
||||
|
@ -25,8 +27,12 @@ import com.ibm.icu.impl.ICUData;
|
|||
import com.ibm.icu.impl.ICUResourceBundle;
|
||||
import com.ibm.icu.impl.Pair;
|
||||
import com.ibm.icu.impl.UResource;
|
||||
import com.ibm.icu.impl.units.MeasureUnitImpl;
|
||||
import com.ibm.icu.impl.units.SingleUnitImpl;
|
||||
import com.ibm.icu.text.UnicodeSet;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A unit such as length, mass, volume, currency, etc. A unit is
|
||||
* coupled with a numeric amount to produce a Measure. MeasureUnit objects are immutable.
|
||||
|
@ -48,6 +54,7 @@ public class MeasureUnit implements Serializable {
|
|||
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.
|
||||
*/
|
||||
|
@ -55,12 +62,255 @@ public class MeasureUnit implements Serializable {
|
|||
protected final String type;
|
||||
|
||||
/**
|
||||
* If subType set to null, measureUnitImpl is in use instead of type and subType.
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
@Deprecated
|
||||
protected final String subType;
|
||||
|
||||
/**
|
||||
* Used by new draft APIs in ICU 68.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
private MeasureUnitImpl measureUnitImpl = null;
|
||||
|
||||
/**
|
||||
* Enumeration for unit complexity. There are three levels:
|
||||
* <p>
|
||||
* - SINGLE: A single unit, optionally with a power and/or SI prefix. Examples: hectare,
|
||||
* square-kilometer, kilojoule, one-per-second.
|
||||
* - COMPOUND: A unit composed of the product of multiple single units. Examples:
|
||||
* meter-per-second, kilowatt-hour, kilogram-meter-per-square-second.
|
||||
* - 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.
|
||||
* <p>
|
||||
* The complexity determines which operations are available. For example, you cannot set the power
|
||||
* or SI prefix of a compound unit.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
public enum Complexity {
|
||||
/**
|
||||
* A single unit, like kilojoule.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
SINGLE,
|
||||
|
||||
/**
|
||||
* A compound unit, like meter-per-second.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
COMPOUND,
|
||||
|
||||
/**
|
||||
* A mixed unit, like hour-and-minute.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
MIXED
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumeration for SI prefixes, such as "kilo".
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
public enum SIPrefix {
|
||||
|
||||
/**
|
||||
* SI prefix: yotta, 10^24.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
YOTTA(24, "yotta"),
|
||||
|
||||
/**
|
||||
* SI prefix: zetta, 10^21.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
ZETTA(21, "zetta"),
|
||||
|
||||
/**
|
||||
* SI prefix: exa, 10^18.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
EXA(18, "exa"),
|
||||
|
||||
/**
|
||||
* SI prefix: peta, 10^15.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
PETA(15, "peta"),
|
||||
|
||||
/**
|
||||
* SI prefix: tera, 10^12.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
TERA(12, "tera"),
|
||||
|
||||
/**
|
||||
* SI prefix: giga, 10^9.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
GIGA(9, "giga"),
|
||||
|
||||
/**
|
||||
* SI prefix: mega, 10^6.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
MEGA(6, "mega"),
|
||||
|
||||
/**
|
||||
* SI prefix: kilo, 10^3.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
KILO(3, "kilo"),
|
||||
|
||||
/**
|
||||
* SI prefix: hecto, 10^2.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
HECTO(2, "hecto"),
|
||||
|
||||
/**
|
||||
* SI prefix: deka, 10^1.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
DEKA(1, "deka"),
|
||||
|
||||
/**
|
||||
* The absence of an SI prefix.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
ONE(0, ""),
|
||||
|
||||
/**
|
||||
* SI prefix: deci, 10^-1.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
DECI(-1, "deci"),
|
||||
|
||||
/**
|
||||
* SI prefix: centi, 10^-2.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
CENTI(-2, "centi"),
|
||||
|
||||
/**
|
||||
* SI prefix: milli, 10^-3.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
MILLI(-3, "milli"),
|
||||
|
||||
/**
|
||||
* SI prefix: micro, 10^-6.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
MICRO(-6, "micro"),
|
||||
|
||||
/**
|
||||
* SI prefix: nano, 10^-9.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
NANO(-9, "nano"),
|
||||
|
||||
/**
|
||||
* SI prefix: pico, 10^-12.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
PICO(-12, "pico"),
|
||||
|
||||
/**
|
||||
* SI prefix: femto, 10^-15.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
FEMTO(-15, "femto"),
|
||||
|
||||
/**
|
||||
* SI prefix: atto, 10^-18.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
ATTO(-18, "atto"),
|
||||
|
||||
/**
|
||||
* SI prefix: zepto, 10^-21.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
ZEPTO(-21, "zepto"),
|
||||
|
||||
/**
|
||||
* SI prefix: yocto, 10^-24.
|
||||
*
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
YOCTO(-24, "yocto");
|
||||
|
||||
private final int siPrefixPower;
|
||||
private final String identifier;
|
||||
|
||||
SIPrefix(int siPrefixPower, String identifier) {
|
||||
this.siPrefixPower = siPrefixPower;
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public int getSiPrefixPower() {
|
||||
return siPrefixPower;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
|
@ -71,6 +321,52 @@ public class MeasureUnit implements Serializable {
|
|||
this.subType = subType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a MeasureUnit from a CLDR Unit Identifier, defined in UTS 35.
|
||||
* Validates and canonicalizes the identifier.
|
||||
*
|
||||
* Note: dimensionless <code>MeasureUnit</code> is <code>null</code>
|
||||
*
|
||||
* <pre>
|
||||
* MeasureUnit example = MeasureUnit::forIdentifier("furlong-per-nanosecond")
|
||||
* </pre>
|
||||
*
|
||||
* @param identifier The CLDR Sequence Unit Identifier
|
||||
* @throws IllegalArgumentException if the identifier is invalid.
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
public static MeasureUnit forIdentifier(String identifier) {
|
||||
if (identifier == null || identifier.isEmpty()) {
|
||||
return NoUnit.BASE;
|
||||
}
|
||||
|
||||
return MeasureUnitImpl.forIdentifier(identifier).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param measureUnitImpl
|
||||
*/
|
||||
public static MeasureUnit fromMeasureUnitImpl(MeasureUnitImpl measureUnitImpl) {
|
||||
measureUnitImpl.serialize();
|
||||
String identifier = measureUnitImpl.getIdentifier();
|
||||
MeasureUnit result = MeasureUnit.findBySubType(identifier);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return new MeasureUnit(measureUnitImpl);
|
||||
}
|
||||
|
||||
private MeasureUnit(MeasureUnitImpl measureUnitImpl) {
|
||||
type = null;
|
||||
subType = null;
|
||||
this.measureUnitImpl = measureUnitImpl.clone();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the type, such as "length"
|
||||
*
|
||||
|
@ -90,7 +386,187 @@ public class MeasureUnit implements Serializable {
|
|||
return subType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the CLDR Unit Identifier for this MeasureUnit, as defined in UTS 35.
|
||||
*
|
||||
* @return The string form of this unit.
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
public String getIdentifier() {
|
||||
String result = measureUnitImpl == null ? getSubtype() : measureUnitImpl.getIdentifier();
|
||||
return result == null ? "" : result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the complexity of the unit. See Complexity for more information.
|
||||
*
|
||||
* @return The unit complexity.
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
public Complexity getComplexity() {
|
||||
if (measureUnitImpl == null) {
|
||||
return MeasureUnitImpl.forIdentifier(getIdentifier()).getComplexity();
|
||||
}
|
||||
|
||||
return measureUnitImpl.getComplexity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a MeasureUnit which is this SINGLE unit augmented with the specified SI prefix.
|
||||
* For example, SI_PREFIX_KILO for "kilo".
|
||||
* May return this if this unit already has that prefix.
|
||||
* <p>
|
||||
* There is sufficient locale data to format all standard SI prefixes.
|
||||
* <p>
|
||||
* NOTE: Only works on SINGLE units. If this is a COMPOUND or MIXED unit, an error will
|
||||
* occur. For more information, see `Complexity`.
|
||||
*
|
||||
* @param prefix The SI prefix, from SIPrefix.
|
||||
* @return A new SINGLE unit.
|
||||
* @throws UnsupportedOperationException if this unit is a COMPOUND or MIXED unit.
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
public MeasureUnit withSIPrefix(SIPrefix prefix) {
|
||||
SingleUnitImpl singleUnit = getSingleUnitImpl();
|
||||
singleUnit.setSiPrefix(prefix);
|
||||
return singleUnit.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current SI prefix of this SINGLE unit. For example, if the unit has the SI prefix
|
||||
* "kilo", then SI_PREFIX_KILO is returned.
|
||||
* <p>
|
||||
* NOTE: Only works on SINGLE units. If this is a COMPOUND or MIXED unit, an error will
|
||||
* occur. For more information, see `Complexity`.
|
||||
*
|
||||
* @return The SI prefix of this SINGLE unit, from SIPrefix.
|
||||
* @throws UnsupportedOperationException if the unit is COMPOUND or MIXED.
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
public SIPrefix getSIPrefix() {
|
||||
return getSingleUnitImpl().getSiPrefix();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* For more information, see `Complexity`.
|
||||
*
|
||||
* @return The dimensionality (power) of this simple unit.
|
||||
* @throws UnsupportedOperationException if the unit is COMPOUND or MIXED.
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
public int getDimensionality() {
|
||||
return getSingleUnitImpl().getDimensionality();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* For more information, see `Complexity`.
|
||||
*
|
||||
* @param dimensionality The dimensionality (power).
|
||||
* @return A new SINGLE unit.
|
||||
* @throws UnsupportedOperationException if the unit is COMPOUND or MIXED.
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
public MeasureUnit withDimensionality(int dimensionality) {
|
||||
SingleUnitImpl singleUnit = getSingleUnitImpl();
|
||||
singleUnit.setDimensionality(dimensionality);
|
||||
return singleUnit.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <p>
|
||||
* NOTE: Only works on SINGLE and COMPOUND units. If this is a MIXED unit, an error will
|
||||
* occur. For more information, see `Complexity`.
|
||||
*
|
||||
* @return The reciprocal of the target unit.
|
||||
* @throws UnsupportedOperationException if the unit is MIXED.
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
public MeasureUnit reciprocal() {
|
||||
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
|
||||
* 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
|
||||
* unit "kilowatt-hour-per-day" is returned.
|
||||
* <p>
|
||||
* 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, see `Complexity`.
|
||||
*
|
||||
* @param other The MeasureUnit to multiply with the target.
|
||||
* @return The product of the target unit with the provided unit.
|
||||
* @throws UnsupportedOperationException if the unit is MIXED.
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
public MeasureUnit product(MeasureUnit other) {
|
||||
MeasureUnitImpl implCopy = getCopyOfMeasureUnitImpl();
|
||||
|
||||
if (other == null /* dimensionless */) {
|
||||
return implCopy.build();
|
||||
}
|
||||
|
||||
final MeasureUnitImpl otherImplRef = other.getMayBeReferenceOfMeasureUnitImpl();
|
||||
if (implCopy.getComplexity() == Complexity.MIXED || otherImplRef.getComplexity() == Complexity.MIXED) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
for (SingleUnitImpl singleUnit :
|
||||
otherImplRef.getSingleUnits()) {
|
||||
implCopy.appendSingleUnit(singleUnit);
|
||||
}
|
||||
|
||||
return implCopy.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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",
|
||||
* "kilogram", and "one-per-second".
|
||||
* - Given "hour+minute+second", three units will be returned: "hour", "minute",
|
||||
* and "second".
|
||||
* <p>
|
||||
* If this is a SINGLE unit, a list of length 1 will be returned.
|
||||
*
|
||||
* @return An unmodifiable list of single units
|
||||
* @internal ICU 68 Technology Preview
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
public List<MeasureUnit> splitToSingleUnits() {
|
||||
final ArrayList<SingleUnitImpl> singleUnits = getMayBeReferenceOfMeasureUnitImpl().getSingleUnits();
|
||||
List<MeasureUnit> result = new ArrayList<>(singleUnits.size());
|
||||
for (SingleUnitImpl singleUnit : singleUnits) {
|
||||
result.add(singleUnit.build());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
|
@ -115,8 +591,8 @@ public class MeasureUnit implements Serializable {
|
|||
if (!(rhs instanceof MeasureUnit)) {
|
||||
return false;
|
||||
}
|
||||
MeasureUnit c = (MeasureUnit) rhs;
|
||||
return type.equals(c.type) && subType.equals(c.subType);
|
||||
|
||||
return this.getIdentifier().equals(((MeasureUnit) rhs).getIdentifier());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1546,6 +2022,39 @@ public class MeasureUnit implements Serializable {
|
|||
return new MeasureUnitProxy(type, subType);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return this object as a SingleUnitImpl.
|
||||
* @throws UnsupportedOperationException if this object could not be converted to a single unit.
|
||||
*/
|
||||
private SingleUnitImpl getSingleUnitImpl() {
|
||||
if (measureUnitImpl == null) {
|
||||
return MeasureUnitImpl.forIdentifier(getIdentifier()).getSingleUnitImpl();
|
||||
}
|
||||
|
||||
return measureUnitImpl.getSingleUnitImpl();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return this object in a MeasureUnitImpl form.
|
||||
*/
|
||||
private MeasureUnitImpl getCopyOfMeasureUnitImpl() {
|
||||
return this.measureUnitImpl == null ?
|
||||
MeasureUnitImpl.forIdentifier(getIdentifier()) :
|
||||
this.measureUnitImpl.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return this object in a MeasureUnitImpl form.
|
||||
*/
|
||||
private MeasureUnitImpl getMayBeReferenceOfMeasureUnitImpl(){
|
||||
return this.measureUnitImpl == null ?
|
||||
MeasureUnitImpl.forIdentifier(getIdentifier()) :
|
||||
this.measureUnitImpl;
|
||||
}
|
||||
|
||||
static final class MeasureUnitProxy implements Externalizable {
|
||||
private static final long serialVersionUID = -3910681415330989598L;
|
||||
|
||||
|
@ -1586,4 +2095,4 @@ public class MeasureUnit implements Serializable {
|
|||
return MeasureUnit.internalGetInstance(type, subType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,33 +8,6 @@
|
|||
*/
|
||||
package com.ibm.icu.dev.test.format;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
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;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import com.ibm.icu.dev.test.TestFmwk;
|
||||
import com.ibm.icu.dev.test.serializable.FormatHandler;
|
||||
import com.ibm.icu.dev.test.serializable.SerializableTestUtility;
|
||||
|
@ -45,13 +18,17 @@ import com.ibm.icu.text.MeasureFormat;
|
|||
import com.ibm.icu.text.MeasureFormat.FormatWidth;
|
||||
import com.ibm.icu.text.NumberFormat;
|
||||
import com.ibm.icu.util.Currency;
|
||||
import com.ibm.icu.util.CurrencyAmount;
|
||||
import com.ibm.icu.util.Measure;
|
||||
import com.ibm.icu.util.MeasureUnit;
|
||||
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.*;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.text.FieldPosition;
|
||||
import java.text.ParseException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* See https://sites.google.com/site/icusite/processes/release/tasks/standards?pli=1
|
||||
|
@ -3485,4 +3462,405 @@ public class MeasureUnitTest extends TestFmwk {
|
|||
fmt = MeasureFormat.getInstance(ULocale.forLanguageTag("da"), FormatWidth.NUMERIC);
|
||||
Assert.assertEquals("2.03,877", fmt.formatMeasures(fhours, fminutes));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestIdentifiers() {
|
||||
class TestCase {
|
||||
final String id;
|
||||
final String normalized;
|
||||
|
||||
TestCase(String id, String normalized) {
|
||||
this.id = id;
|
||||
this.normalized = normalized;
|
||||
}
|
||||
}
|
||||
|
||||
TestCase cases[] = {
|
||||
// Correctly normalized identifiers should not change
|
||||
new TestCase("square-meter-per-square-meter", "square-meter-per-square-meter"),
|
||||
new TestCase("kilogram-meter-per-square-meter-square-second",
|
||||
"kilogram-meter-per-square-meter-square-second"),
|
||||
new TestCase("square-mile-and-square-foot", "square-mile-and-square-foot"),
|
||||
new TestCase("square-foot-and-square-mile", "square-foot-and-square-mile"),
|
||||
new TestCase("per-cubic-centimeter", "per-cubic-centimeter"),
|
||||
new TestCase("per-kilometer", "per-kilometer"),
|
||||
|
||||
// Normalization of power and per
|
||||
new TestCase(
|
||||
"pow2-foot-and-pow2-mile", "square-foot-and-square-mile"),
|
||||
new TestCase(
|
||||
"gram-square-gram-per-dekagram", "cubic-gram-per-dekagram"),
|
||||
new TestCase(
|
||||
"kilogram-per-meter-per-second", "kilogram-per-meter-second"),
|
||||
|
||||
// TODO(ICU-20920): Add more test cases once the proper ranking is available.
|
||||
};
|
||||
|
||||
|
||||
for (TestCase testCase : cases) {
|
||||
MeasureUnit unit = MeasureUnit.forIdentifier(testCase.id);
|
||||
|
||||
final String actual = unit.getIdentifier();
|
||||
assertEquals(testCase.id, testCase.normalized, actual);
|
||||
}
|
||||
|
||||
assertEquals("for empty identifiers, the MeasureUnit will be null",
|
||||
null, MeasureUnit.forIdentifier(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestInvalidIdentifiers() {
|
||||
final String inputs[] = {
|
||||
"kilo",
|
||||
"kilokilo",
|
||||
"onekilo",
|
||||
"meterkilo",
|
||||
"meter-kilo",
|
||||
"k",
|
||||
"meter-",
|
||||
"meter+",
|
||||
"-meter",
|
||||
"+meter",
|
||||
"-kilometer",
|
||||
"+kilometer",
|
||||
"-pow2-meter",
|
||||
"+pow2-meter",
|
||||
"p2-meter",
|
||||
"p4-meter",
|
||||
"+",
|
||||
"-",
|
||||
"-mile",
|
||||
"-and-mile",
|
||||
"-per-mile",
|
||||
"one",
|
||||
"one-one",
|
||||
"one-per-mile",
|
||||
"one-per-cubic-centimeter",
|
||||
"square--per-meter",
|
||||
"metersecond", // Must have compound part in between single units
|
||||
|
||||
// Negative powers not supported in mixed units yet. TODO(CLDR-13701).
|
||||
"per-hour-and-hertz",
|
||||
"hertz-and-per-hour",
|
||||
|
||||
// Compound units not supported in mixed units yet. TODO(CLDR-13700).
|
||||
"kilonewton-meter-and-newton-meter",
|
||||
};
|
||||
|
||||
for (String input : inputs) {
|
||||
try {
|
||||
MeasureUnit.forIdentifier(input);
|
||||
Assert.fail("An IllegalArgumentException must be thrown");
|
||||
} catch (IllegalArgumentException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestCompoundUnitOperations() {
|
||||
MeasureUnit.forIdentifier("kilometer-per-second-joule");
|
||||
|
||||
MeasureUnit kilometer = MeasureUnit.KILOMETER;
|
||||
MeasureUnit cubicMeter = MeasureUnit.CUBIC_METER;
|
||||
MeasureUnit meter = kilometer.withSIPrefix(MeasureUnit.SIPrefix.ONE);
|
||||
MeasureUnit centimeter1 = kilometer.withSIPrefix(MeasureUnit.SIPrefix.CENTI);
|
||||
MeasureUnit centimeter2 = meter.withSIPrefix(MeasureUnit.SIPrefix.CENTI);
|
||||
MeasureUnit cubicDecimeter = cubicMeter.withSIPrefix(MeasureUnit.SIPrefix.DECI);
|
||||
|
||||
verifySingleUnit(kilometer, MeasureUnit.SIPrefix.KILO, 1, "kilometer");
|
||||
verifySingleUnit(meter, MeasureUnit.SIPrefix.ONE, 1, "meter");
|
||||
verifySingleUnit(centimeter1, MeasureUnit.SIPrefix.CENTI, 1, "centimeter");
|
||||
verifySingleUnit(centimeter2, MeasureUnit.SIPrefix.CENTI, 1, "centimeter");
|
||||
verifySingleUnit(cubicDecimeter, MeasureUnit.SIPrefix.DECI, 3, "cubic-decimeter");
|
||||
|
||||
assertTrue("centimeter equality", centimeter1.equals( centimeter2));
|
||||
assertTrue("kilometer inequality", !centimeter1.equals( kilometer));
|
||||
|
||||
MeasureUnit squareMeter = meter.withDimensionality(2);
|
||||
MeasureUnit overCubicCentimeter = centimeter1.withDimensionality(-3);
|
||||
MeasureUnit quarticKilometer = kilometer.withDimensionality(4);
|
||||
MeasureUnit overQuarticKilometer1 = kilometer.withDimensionality(-4);
|
||||
|
||||
verifySingleUnit(squareMeter, MeasureUnit.SIPrefix.ONE, 2, "square-meter");
|
||||
verifySingleUnit(overCubicCentimeter, MeasureUnit.SIPrefix.CENTI, -3, "per-cubic-centimeter");
|
||||
verifySingleUnit(quarticKilometer, MeasureUnit.SIPrefix.KILO, 4, "pow4-kilometer");
|
||||
verifySingleUnit(overQuarticKilometer1, MeasureUnit.SIPrefix.KILO, -4, "per-pow4-kilometer");
|
||||
|
||||
assertTrue("power inequality", quarticKilometer != overQuarticKilometer1);
|
||||
|
||||
MeasureUnit overQuarticKilometer2 = quarticKilometer.reciprocal();
|
||||
MeasureUnit overQuarticKilometer3 = kilometer.product(kilometer)
|
||||
.product(kilometer)
|
||||
.product(kilometer)
|
||||
.reciprocal();
|
||||
MeasureUnit overQuarticKilometer4 = meter.withDimensionality(4)
|
||||
.reciprocal()
|
||||
.withSIPrefix(MeasureUnit.SIPrefix.KILO);
|
||||
|
||||
verifySingleUnit(overQuarticKilometer2, MeasureUnit.SIPrefix.KILO, -4, "per-pow4-kilometer");
|
||||
verifySingleUnit(overQuarticKilometer3, MeasureUnit.SIPrefix.KILO, -4, "per-pow4-kilometer");
|
||||
verifySingleUnit(overQuarticKilometer4, MeasureUnit.SIPrefix.KILO, -4, "per-pow4-kilometer");
|
||||
|
||||
assertTrue("reciprocal equality", overQuarticKilometer1.equals(overQuarticKilometer2));
|
||||
assertTrue("reciprocal equality", overQuarticKilometer1.equals(overQuarticKilometer3));
|
||||
assertTrue("reciprocal equality", overQuarticKilometer1.equals(overQuarticKilometer4));
|
||||
|
||||
MeasureUnit kiloSquareSecond = MeasureUnit.SECOND
|
||||
.withDimensionality(2).withSIPrefix(MeasureUnit.SIPrefix.KILO);
|
||||
MeasureUnit meterSecond = meter.product(kiloSquareSecond);
|
||||
MeasureUnit cubicMeterSecond1 = meter.withDimensionality(3).product(kiloSquareSecond);
|
||||
MeasureUnit centimeterSecond1 = meter.withSIPrefix(MeasureUnit.SIPrefix.CENTI).product(kiloSquareSecond);
|
||||
MeasureUnit secondCubicMeter = kiloSquareSecond.product(meter.withDimensionality(3));
|
||||
MeasureUnit secondCentimeter = kiloSquareSecond.product(meter.withSIPrefix(MeasureUnit.SIPrefix.CENTI));
|
||||
MeasureUnit secondCentimeterPerKilometer = secondCentimeter.product(kilometer.reciprocal());
|
||||
|
||||
verifySingleUnit(kiloSquareSecond, MeasureUnit.SIPrefix.KILO, 2, "square-kilosecond");
|
||||
String meterSecondSub[] = {
|
||||
"meter", "square-kilosecond"
|
||||
};
|
||||
verifyCompoundUnit(meterSecond, "meter-square-kilosecond",
|
||||
meterSecondSub, meterSecondSub.length);
|
||||
String cubicMeterSecond1Sub[] = {
|
||||
"cubic-meter", "square-kilosecond"
|
||||
};
|
||||
verifyCompoundUnit(cubicMeterSecond1, "cubic-meter-square-kilosecond",
|
||||
cubicMeterSecond1Sub, cubicMeterSecond1Sub.length);
|
||||
String centimeterSecond1Sub[] = {
|
||||
"centimeter", "square-kilosecond"
|
||||
};
|
||||
verifyCompoundUnit(centimeterSecond1, "centimeter-square-kilosecond",
|
||||
centimeterSecond1Sub, centimeterSecond1Sub.length);
|
||||
String secondCubicMeterSub[] = {
|
||||
"cubic-meter", "square-kilosecond"
|
||||
};
|
||||
verifyCompoundUnit(secondCubicMeter, "cubic-meter-square-kilosecond",
|
||||
secondCubicMeterSub, secondCubicMeterSub.length);
|
||||
String secondCentimeterSub[] = {
|
||||
"centimeter", "square-kilosecond"
|
||||
};
|
||||
verifyCompoundUnit(secondCentimeter, "centimeter-square-kilosecond",
|
||||
secondCentimeterSub, secondCentimeterSub.length);
|
||||
String secondCentimeterPerKilometerSub[] = {
|
||||
"centimeter", "square-kilosecond", "per-kilometer"
|
||||
};
|
||||
verifyCompoundUnit(secondCentimeterPerKilometer, "centimeter-square-kilosecond-per-kilometer",
|
||||
secondCentimeterPerKilometerSub, secondCentimeterPerKilometerSub.length);
|
||||
|
||||
assertTrue("reordering equality", cubicMeterSecond1.equals(secondCubicMeter));
|
||||
assertTrue("additional simple units inequality", !secondCubicMeter.equals(secondCentimeter));
|
||||
|
||||
// Don't allow get/set power or SI prefix on compound units
|
||||
try {
|
||||
meterSecond.getDimensionality();
|
||||
fail("UnsupportedOperationException must be thrown");
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// Expecting an exception to be thrown
|
||||
}
|
||||
|
||||
try {
|
||||
meterSecond.withDimensionality(3);
|
||||
fail("UnsupportedOperationException must be thrown");
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// Expecting an exception to be thrown
|
||||
}
|
||||
|
||||
try {
|
||||
meterSecond.getSIPrefix();
|
||||
fail("UnsupportedOperationException must be thrown");
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// Expecting an exception to be thrown
|
||||
}
|
||||
|
||||
try {
|
||||
meterSecond.withSIPrefix(MeasureUnit.SIPrefix.CENTI);
|
||||
fail("UnsupportedOperationException must be thrown");
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// Expecting an exception to be thrown
|
||||
}
|
||||
|
||||
MeasureUnit footInch = MeasureUnit.forIdentifier("foot-and-inch");
|
||||
MeasureUnit inchFoot = MeasureUnit.forIdentifier("inch-and-foot");
|
||||
|
||||
String footInchSub[] = {
|
||||
"foot", "inch"
|
||||
};
|
||||
verifyMixedUnit(footInch, "foot-and-inch",
|
||||
footInchSub, footInchSub.length);
|
||||
String inchFootSub[] = {
|
||||
"inch", "foot"
|
||||
};
|
||||
verifyMixedUnit(inchFoot, "inch-and-foot",
|
||||
inchFootSub, inchFootSub.length);
|
||||
|
||||
assertTrue("order matters inequality", !footInch.equals(inchFoot));
|
||||
|
||||
|
||||
MeasureUnit dimensionless = NoUnit.BASE;
|
||||
MeasureUnit dimensionless2 = MeasureUnit.forIdentifier("");
|
||||
assertEquals("dimensionless equality", dimensionless, dimensionless2);
|
||||
|
||||
// We support starting from an "identity" MeasureUnit and then combining it
|
||||
// with others via product:
|
||||
MeasureUnit kilometer2 = kilometer.product(dimensionless);
|
||||
|
||||
verifySingleUnit(kilometer2, MeasureUnit.SIPrefix.KILO, 1, "kilometer");
|
||||
assertTrue("kilometer equality", kilometer.equals(kilometer2));
|
||||
|
||||
// Test out-of-range powers
|
||||
MeasureUnit power15 = MeasureUnit.forIdentifier("pow15-kilometer");
|
||||
verifySingleUnit(power15, MeasureUnit.SIPrefix.KILO, 15, "pow15-kilometer");
|
||||
|
||||
try {
|
||||
MeasureUnit.forIdentifier("pow16-kilometer");
|
||||
fail("An IllegalArgumentException must be thrown");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Expecting an exception to be thrown
|
||||
}
|
||||
|
||||
try {
|
||||
power15.product(kilometer);
|
||||
fail("An IllegalArgumentException must be thrown");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Expecting an exception to be thrown
|
||||
}
|
||||
|
||||
MeasureUnit powerN15 = MeasureUnit.forIdentifier("per-pow15-kilometer");
|
||||
verifySingleUnit(powerN15, MeasureUnit.SIPrefix.KILO, -15, "per-pow15-kilometer");
|
||||
|
||||
try {
|
||||
MeasureUnit.forIdentifier("per-pow16-kilometer");
|
||||
fail("An IllegalArgumentException must be thrown");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Expecting an exception to be thrown
|
||||
}
|
||||
|
||||
try {
|
||||
powerN15.product(overQuarticKilometer1);
|
||||
fail("An IllegalArgumentException must be thrown");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Expecting an exception to be thrown
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestDimensionlessBehaviour() {
|
||||
MeasureUnit dimensionless = MeasureUnit.forIdentifier("");
|
||||
MeasureUnit dimensionless2 = NoUnit.BASE;
|
||||
MeasureUnit dimensionless3 = null;
|
||||
MeasureUnit dimensionless4 = MeasureUnit.forIdentifier(null);
|
||||
|
||||
assertEquals("dimensionless must be equals", dimensionless, dimensionless2);
|
||||
assertEquals("dimensionless must be equals", dimensionless2, dimensionless3);
|
||||
assertEquals("dimensionless must be equals", dimensionless3, dimensionless4);
|
||||
|
||||
// product(dimensionless)
|
||||
MeasureUnit mile = MeasureUnit.MILE;
|
||||
mile = mile.product(dimensionless);
|
||||
verifySingleUnit(mile, MeasureUnit.SIPrefix.ONE, 1, "mile");
|
||||
}
|
||||
|
||||
private void verifySingleUnit(MeasureUnit singleMeasureUnit, MeasureUnit.SIPrefix prefix, int power, String identifier) {
|
||||
assertEquals(identifier + ": SI prefix", prefix, singleMeasureUnit.getSIPrefix());
|
||||
|
||||
assertEquals(identifier + ": Power", power, singleMeasureUnit.getDimensionality());
|
||||
|
||||
assertEquals(identifier + ": Identifier", identifier, singleMeasureUnit.getIdentifier());
|
||||
|
||||
assertTrue(identifier + ": Constructor", singleMeasureUnit.equals(MeasureUnit.forIdentifier(identifier)));
|
||||
|
||||
assertEquals(identifier + ": Complexity", MeasureUnit.Complexity.SINGLE, singleMeasureUnit.getComplexity());
|
||||
}
|
||||
|
||||
|
||||
// Kilogram is a "base unit", although it's also "gram" with a kilo- prefix.
|
||||
// This tests that it is handled in the preferred manner.
|
||||
@Test
|
||||
public void TestKilogramIdentifier() {
|
||||
// SI unit of mass
|
||||
MeasureUnit kilogram = MeasureUnit.forIdentifier("kilogram");
|
||||
// Metric mass unit
|
||||
MeasureUnit gram = MeasureUnit.forIdentifier("gram");
|
||||
// Microgram: still a built-in type
|
||||
MeasureUnit microgram = MeasureUnit.forIdentifier("microgram");
|
||||
// Nanogram: not a built-in type at this time
|
||||
MeasureUnit nanogram = MeasureUnit.forIdentifier("nanogram");
|
||||
|
||||
assertEquals("parsed kilogram equals built-in kilogram", MeasureUnit.KILOGRAM.getType(),
|
||||
kilogram.getType());
|
||||
assertEquals("parsed kilogram equals built-in kilogram", MeasureUnit.KILOGRAM.getSubtype(),
|
||||
kilogram.getSubtype());
|
||||
assertEquals("parsed gram equals built-in gram", MeasureUnit.GRAM.getType(), gram.getType());
|
||||
assertEquals("parsed gram equals built-in gram", MeasureUnit.GRAM.getSubtype(),
|
||||
gram.getSubtype());
|
||||
assertEquals("parsed microgram equals built-in microgram", MeasureUnit.MICROGRAM.getType(),
|
||||
microgram.getType());
|
||||
assertEquals("parsed microgram equals built-in microgram", MeasureUnit.MICROGRAM.getSubtype(),
|
||||
microgram.getSubtype());
|
||||
assertEquals("nanogram", null, nanogram.getType());
|
||||
assertEquals("nanogram", "nanogram", nanogram.getIdentifier());
|
||||
|
||||
assertEquals("prefix of kilogram", MeasureUnit.SIPrefix.KILO, kilogram.getSIPrefix());
|
||||
assertEquals("prefix of gram", MeasureUnit.SIPrefix.ONE, gram.getSIPrefix());
|
||||
assertEquals("prefix of microgram", MeasureUnit.SIPrefix.MICRO, microgram.getSIPrefix());
|
||||
assertEquals("prefix of nanogram", MeasureUnit.SIPrefix.NANO, nanogram.getSIPrefix());
|
||||
|
||||
MeasureUnit tmp = kilogram.withSIPrefix(MeasureUnit.SIPrefix.MILLI);
|
||||
assertEquals("Kilogram + milli should be milligram, got: " + tmp.getIdentifier(),
|
||||
MeasureUnit.MILLIGRAM.getIdentifier(), tmp.getIdentifier());
|
||||
}
|
||||
|
||||
private void verifyCompoundUnit(
|
||||
MeasureUnit unit,
|
||||
String identifier,
|
||||
String subIdentifiers[],
|
||||
int subIdentifierCount) {
|
||||
assertEquals(identifier + ": Identifier",
|
||||
identifier,
|
||||
unit.getIdentifier());
|
||||
|
||||
assertTrue(identifier + ": Constructor",
|
||||
unit.equals(MeasureUnit.forIdentifier(identifier)));
|
||||
|
||||
assertEquals(identifier + ": Complexity",
|
||||
MeasureUnit.Complexity.COMPOUND,
|
||||
unit.getComplexity());
|
||||
|
||||
List<MeasureUnit> subUnits = unit.splitToSingleUnits();
|
||||
assertEquals(identifier + ": Length", subIdentifierCount, subUnits.size());
|
||||
for (int i = 0; ; i++) {
|
||||
if (i >= subIdentifierCount || i >= subUnits.size()) break;
|
||||
assertEquals(identifier + ": Sub-unit #" + i,
|
||||
subIdentifiers[i],
|
||||
subUnits.get(i).getIdentifier());
|
||||
assertEquals(identifier + ": Sub-unit Complexity",
|
||||
MeasureUnit.Complexity.SINGLE,
|
||||
subUnits.get(i).getComplexity());
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyMixedUnit(
|
||||
MeasureUnit unit,
|
||||
String identifier,
|
||||
String subIdentifiers[],
|
||||
int subIdentifierCount) {
|
||||
assertEquals(identifier + ": Identifier",
|
||||
identifier,
|
||||
unit.getIdentifier());
|
||||
assertTrue(identifier + ": Constructor",
|
||||
unit.equals(MeasureUnit.forIdentifier(identifier)));
|
||||
|
||||
assertEquals(identifier + ": Complexity",
|
||||
MeasureUnit.Complexity.MIXED,
|
||||
unit.getComplexity());
|
||||
|
||||
List<MeasureUnit> subUnits = unit.splitToSingleUnits();
|
||||
assertEquals(identifier + ": Length", subIdentifierCount, subUnits.size());
|
||||
for (int i = 0; ; i++) {
|
||||
if (i >= subIdentifierCount || i >= subUnits.size()) break;
|
||||
assertEquals(identifier + ": Sub-unit #" + i,
|
||||
subIdentifiers[i],
|
||||
subUnits.get(i).getIdentifier());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue