201 lines
8.8 KiB
Java
201 lines
8.8 KiB
Java
/* GENERATED SOURCE. DO NOT MODIFY. */
|
|
// © 2020 and later: Unicode, Inc. and others.
|
|
// License & terms of use: http://www.unicode.org/copyright.html
|
|
package android.icu.impl.units;
|
|
|
|
import java.math.BigDecimal;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
import android.icu.impl.IllegalIcuArgumentException;
|
|
import android.icu.impl.number.MicroProps;
|
|
import android.icu.number.Precision;
|
|
import android.icu.util.MeasureUnit;
|
|
import android.icu.util.ULocale;
|
|
|
|
/**
|
|
* {@code UnitsRouter} responsible for converting from a single unit (such as {@code meter} or
|
|
* {@code meter-per-second}) to one of the complex units based on the limits.
|
|
* For example:
|
|
* if the input is {@code meter} and the output as following
|
|
* {{@code foot+inch}, limit: 3.0}
|
|
* {{@code inch} , limit: no value (-inf)}
|
|
* Thus means if the input in {@code meter} is greater than or equal to {@code 3.0 feet},
|
|
* the output will be in {@code foot+inch}, otherwise, the output will be in {@code inch}.
|
|
* <p>
|
|
* NOTE:
|
|
* the output units and their limits MUST BE in order, for example, if the output units, from the
|
|
* previous example, are the following:
|
|
* {{@code inch} , limit: no value (-inf)}
|
|
* {{@code foot+inch}, limit: 3.0}
|
|
* IN THIS CASE THE OUTPUT WILL BE ALWAYS IN {@code inch}.
|
|
* <p>
|
|
* NOTE:
|
|
* the output units and their limits will be extracted from the units preferences database by knowing
|
|
* the followings:
|
|
* - input unit
|
|
* - locale
|
|
* - usage
|
|
* <p>
|
|
* DESIGN:
|
|
* {@code UnitRouter} uses internally {@code ComplexUnitConverter} in order to convert the input
|
|
* units to the desired complex units and to check the limit too.
|
|
* @hide Only a subset of ICU is exposed in Android
|
|
*/
|
|
public class UnitsRouter {
|
|
// List of possible output units. TODO: converterPreferences_ now also has
|
|
// this data available. Maybe drop outputUnits_ and have getOutputUnits
|
|
// construct a the list from data in converterPreferences_ instead?
|
|
private ArrayList<MeasureUnit> outputUnits_ = new ArrayList<>();
|
|
private ArrayList<ConverterPreference> converterPreferences_ = new ArrayList<>();
|
|
|
|
public UnitsRouter(String inputUnitIdentifier, ULocale locale, String usage) {
|
|
this(MeasureUnitImpl.forIdentifier(inputUnitIdentifier), locale, usage);
|
|
}
|
|
|
|
public UnitsRouter(MeasureUnitImpl inputUnit, ULocale locale, String usage) {
|
|
// TODO: do we want to pass in ConversionRates and UnitPreferences instead?
|
|
// of loading in each UnitsRouter instance? (Or make global?)
|
|
UnitsData data = new UnitsData();
|
|
|
|
String category = data.getCategory(inputUnit);
|
|
UnitPreferences.UnitPreference[] unitPreferences = data.getPreferencesFor(category, usage, locale);
|
|
|
|
for (int i = 0; i < unitPreferences.length; ++i) {
|
|
UnitPreferences.UnitPreference preference = unitPreferences[i];
|
|
|
|
MeasureUnitImpl complexTargetUnitImpl =
|
|
MeasureUnitImpl.UnitsParser.parseForIdentifier(preference.getUnit());
|
|
|
|
String precision = preference.getSkeleton();
|
|
|
|
// For now, we only have "precision-increment" in Units Preferences skeleton.
|
|
// Therefore, we check if the skeleton starts with "precision-increment" and force the program to
|
|
// fail otherwise.
|
|
// NOTE:
|
|
// It is allowed to have an empty precision.
|
|
if (!precision.isEmpty() && !precision.startsWith("precision-increment")) {
|
|
throw new AssertionError("Only `precision-increment` is allowed");
|
|
}
|
|
|
|
outputUnits_.add(complexTargetUnitImpl.build());
|
|
converterPreferences_.add(new ConverterPreference(inputUnit, complexTargetUnitImpl,
|
|
preference.getGeq(), precision,
|
|
data.getConversionRates()));
|
|
}
|
|
}
|
|
|
|
/** If micros.rounder is a BogusRounder, this function replaces it with a valid one. */
|
|
public RouteResult route(BigDecimal quantity, MicroProps micros) {
|
|
Precision rounder = micros == null ? null : micros.rounder;
|
|
ConverterPreference converterPreference = null;
|
|
for (ConverterPreference itr : converterPreferences_) {
|
|
converterPreference = itr;
|
|
if (converterPreference.converter.greaterThanOrEqual(quantity.abs(),
|
|
converterPreference.limit)) {
|
|
break;
|
|
}
|
|
}
|
|
assert converterPreference != null;
|
|
assert converterPreference.precision != null;
|
|
|
|
// Set up the rounder for this preference's precision
|
|
if (rounder != null && rounder instanceof Precision.BogusRounder) {
|
|
Precision.BogusRounder bogus = (Precision.BogusRounder)rounder;
|
|
if (converterPreference.precision.length() > 0) {
|
|
rounder = bogus.into(parseSkeletonToPrecision(converterPreference.precision));
|
|
} else {
|
|
// We use the same rounding mode as COMPACT notation: known to be a
|
|
// human-friendly rounding mode: integers, but add a decimal digit
|
|
// as needed to ensure we have at least 2 significant digits.
|
|
rounder = bogus.into(Precision.integer().withMinDigits(2));
|
|
}
|
|
}
|
|
|
|
if (micros != null) {
|
|
micros.rounder = rounder;
|
|
}
|
|
return new RouteResult(
|
|
converterPreference.converter.convert(quantity, rounder),
|
|
converterPreference.targetUnit
|
|
);
|
|
}
|
|
|
|
private static Precision parseSkeletonToPrecision(String precisionSkeleton) {
|
|
final String kSkeletonPrefix = "precision-increment/";
|
|
if (!precisionSkeleton.startsWith(kSkeletonPrefix)) {
|
|
throw new IllegalIcuArgumentException("precisionSkeleton is only precision-increment");
|
|
}
|
|
|
|
// TODO(icu-units#104): the C++ code uses a more sophisticated
|
|
// parseIncrementOption which supports "withMinFraction" - e.g.
|
|
// "precision-increment/0.5". Test with a unit preference that uses
|
|
// this, and fix Java.
|
|
String incrementValue = precisionSkeleton.substring(kSkeletonPrefix.length());
|
|
return Precision.increment(new BigDecimal(incrementValue));
|
|
}
|
|
|
|
/**
|
|
* Returns the list of possible output units, i.e. the full set of
|
|
* preferences, for the localized, usage-specific unit preferences.
|
|
* <p>
|
|
* The returned pointer should be valid for the lifetime of the
|
|
* UnitsRouter instance.
|
|
*/
|
|
public List<MeasureUnit> getOutputUnits() {
|
|
return this.outputUnits_;
|
|
}
|
|
|
|
/**
|
|
* Contains the complex unit converter and the limit which representing the smallest value that the
|
|
* converter should accept. For example, if the converter is converting to {@code foot+inch} and the
|
|
* limit equals 3.0, thus means the converter should not convert to a value less than {@code 3.0 feet}.
|
|
* <p>
|
|
* NOTE:
|
|
* if the limit doest not has a value (i.e. {@code std::numeric_limits<double>::lowest()}),
|
|
* this mean there is no limit for the converter.
|
|
* @hide Only a subset of ICU is exposed in Android
|
|
*/
|
|
public static class ConverterPreference {
|
|
// The output unit for this ConverterPreference. This may be a MIXED unit -
|
|
// for example: "yard-and-foot-and-inch".
|
|
final MeasureUnitImpl targetUnit;
|
|
final ComplexUnitsConverter converter;
|
|
final BigDecimal limit;
|
|
final String precision;
|
|
|
|
// In case there is no limit, the limit will be -inf.
|
|
public ConverterPreference(MeasureUnitImpl source, MeasureUnitImpl targetUnit,
|
|
String precision, ConversionRates conversionRates) {
|
|
this(source, targetUnit, BigDecimal.valueOf(Double.MIN_VALUE), precision,
|
|
conversionRates);
|
|
}
|
|
|
|
public ConverterPreference(MeasureUnitImpl source, MeasureUnitImpl targetUnit,
|
|
BigDecimal limit, String precision, ConversionRates conversionRates) {
|
|
this.converter = new ComplexUnitsConverter(source, targetUnit, conversionRates);
|
|
this.limit = limit;
|
|
this.precision = precision;
|
|
this.targetUnit = targetUnit;
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @hide Only a subset of ICU is exposed in Android
|
|
*/
|
|
public class RouteResult {
|
|
public final ComplexUnitsConverter.ComplexConverterResult complexConverterResult;
|
|
|
|
// The output unit for this RouteResult. This may be a MIXED unit - for
|
|
// example: "yard-and-foot-and-inch", for which `measures` will have three
|
|
// elements.
|
|
public final MeasureUnitImpl outputUnit;
|
|
|
|
RouteResult(ComplexUnitsConverter.ComplexConverterResult complexConverterResult, MeasureUnitImpl outputUnit) {
|
|
this.complexConverterResult = complexConverterResult;
|
|
this.outputUnit = outputUnit;
|
|
}
|
|
}
|
|
}
|