com.ibm.icu.impl.units.ConversionRates Maven / Gradle / Ivy
Show all versions of icu4j Show documentation
// © 2020 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
package com.ibm.icu.impl.units;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.ArrayList;
import java.util.HashMap;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.IllegalIcuArgumentException;
import com.ibm.icu.impl.UResource;
import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.UResourceBundle;
public class ConversionRates {
/**
* Map from any simple unit (i.e. "meter", "foot", "inch") to its basic/root conversion rate info.
*/
private HashMap mapToConversionRate;
public ConversionRates() {
// Read the conversion rates from the data (units.txt).
ICUResourceBundle resource;
resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, "units");
ConversionRatesSink sink = new ConversionRatesSink();
resource.getAllItemsWithFallback(UnitsData.Constants.CONVERSION_UNIT_TABLE_NAME, sink);
this.mapToConversionRate = sink.getMapToConversionRate();
}
/**
* Extracts the factor from a {@code SingleUnitImpl} to its Basic Unit.
*
* @param singleUnit
* @return
*/
// In ICU4C, this is called loadCompoundFactor().
private UnitsConverter.Factor getFactorToBase(SingleUnitImpl singleUnit) {
int power = singleUnit.getDimensionality();
MeasureUnit.MeasurePrefix unitPrefix = singleUnit.getPrefix();
UnitsConverter.Factor result = UnitsConverter.Factor.processFactor(mapToConversionRate.get(singleUnit.getSimpleUnitID()).getConversionRate());
// Prefix before power, because:
// - square-kilometer to square-meter: (1000)^2
// - square-kilometer to square-foot (approximate): (3.28*1000)^2
return result.applyPrefix(unitPrefix).power(power);
}
public UnitsConverter.Factor getFactorToBase(MeasureUnitImpl measureUnit) {
UnitsConverter.Factor result = new UnitsConverter.Factor();
for (SingleUnitImpl singleUnit :
measureUnit.getSingleUnits()) {
result = result.multiply(getFactorToBase(singleUnit));
}
return result;
}
// In ICU4C, this functionality is found in loadConversionRate().
protected BigDecimal getOffset(MeasureUnitImpl source, MeasureUnitImpl target, UnitsConverter.Factor
sourceToBase, UnitsConverter.Factor targetToBase, UnitsConverter.Convertibility convertibility) {
if (convertibility != UnitsConverter.Convertibility.CONVERTIBLE) return BigDecimal.valueOf(0);
if (!(checkSimpleUnit(source) && checkSimpleUnit(target))) return BigDecimal.valueOf(0);
String sourceSimpleIdentifier = source.getSingleUnits().get(0).getSimpleUnitID();
String targetSimpleIdentifier = target.getSingleUnits().get(0).getSimpleUnitID();
BigDecimal sourceOffset = this.mapToConversionRate.get(sourceSimpleIdentifier).getOffset();
BigDecimal targetOffset = this.mapToConversionRate.get(targetSimpleIdentifier).getOffset();
return sourceOffset
.subtract(targetOffset)
.divide(targetToBase.getConversionRate(), MathContext.DECIMAL128);
}
// Map the MeasureUnitImpl for a simple unit to its corresponding SimpleUnitID,
// then get the specialMappingName for that SimpleUnitID (which may be null if
// the simple unit converts to base using factor + offset instelad of a special mapping).
protected String getSpecialMappingName(MeasureUnitImpl simpleUnit) {
if (!checkSimpleUnit(simpleUnit)) return null;
String simpleIdentifier = simpleUnit.getSingleUnits().get(0).getSimpleUnitID();
return this.mapToConversionRate.get(simpleIdentifier).getSpecialMappingName();
}
public MeasureUnitImpl extractCompoundBaseUnit(MeasureUnitImpl measureUnit) {
ArrayList baseUnits = this.extractBaseUnits(measureUnit);
MeasureUnitImpl result = new MeasureUnitImpl();
for (SingleUnitImpl baseUnit :
baseUnits) {
result.appendSingleUnit(baseUnit);
}
return result;
}
public ArrayList extractBaseUnits(MeasureUnitImpl measureUnitImpl) {
ArrayList result = new ArrayList<>();
ArrayList singleUnits = measureUnitImpl.getSingleUnits();
for (SingleUnitImpl singleUnit :
singleUnits) {
result.addAll(extractBaseUnits(singleUnit));
}
return result;
}
/**
* @param singleUnit An instance of SingleUnitImpl.
* @return The base units in the {@code SingleUnitImpl} with applying the dimensionality only and not the SI prefix.
*
* NOTE:
* This method is helpful when checking the convertibility because no need to check convertibility.
*/
public ArrayList extractBaseUnits(SingleUnitImpl singleUnit) {
String target = mapToConversionRate.get(singleUnit.getSimpleUnitID()).getTarget();
MeasureUnitImpl targetImpl = MeasureUnitImpl.UnitsParser.parseForIdentifier(target);
// Each unit must be powered by the same dimension
targetImpl.applyDimensionality(singleUnit.getDimensionality());
// NOTE: we do not apply SI prefixes.
return targetImpl.getSingleUnits();
}
/**
* @return The measurement systems for the specified unit.
*/
public String extractSystems(SingleUnitImpl singleUnit) {
return mapToConversionRate.get(singleUnit.getSimpleUnitID()).getSystems();
}
/**
* Checks if the {@code MeasureUnitImpl} is simple or not.
*
* @param measureUnitImpl
* @return true if the {@code MeasureUnitImpl} is simple, false otherwise.
*/
private boolean checkSimpleUnit(MeasureUnitImpl measureUnitImpl) {
if (measureUnitImpl.getComplexity() != MeasureUnit.Complexity.SINGLE) return false;
SingleUnitImpl singleUnit = measureUnitImpl.getSingleUnits().get(0);
if (singleUnit.getPrefix() != MeasureUnit.MeasurePrefix.ONE) return false;
if (singleUnit.getDimensionality() != 1) return false;
return true;
}
public static class ConversionRatesSink extends UResource.Sink {
/**
* Map from any simple unit (i.e. "meter", "foot", "inch") to its basic/root conversion rate info.
*/
private HashMap mapToConversionRate = new HashMap<>();
@Override
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
assert (UnitsData.Constants.CONVERSION_UNIT_TABLE_NAME.equals(key.toString()));
UResource.Table conversionRateTable = value.getTable();
for (int i = 0; conversionRateTable.getKeyAndValue(i, key, value); i++) {
assert (value.getType() == UResourceBundle.TABLE);
String simpleUnit = key.toString();
UResource.Table simpleUnitConversionInfo = value.getTable();
String target = null;
String factor = null;
String offset = "0";
String special = null;
String systems = null;
for (int j = 0; simpleUnitConversionInfo.getKeyAndValue(j, key, value); j++) {
assert (value.getType() == UResourceBundle.STRING);
String keyString = key.toString();
String valueString = value.toString().replaceAll(" ", "");
if ("target".equals(keyString)) {
target = valueString;
} else if ("factor".equals(keyString)) {
factor = valueString;
} else if ("offset".equals(keyString)) {
offset = valueString;
} else if ("special".equals(keyString)) {
special = valueString; // the name of a special mapping used instead of factor + optional offset.
} else if ("systems".equals(keyString)) {
systems = value.toString(); // still want the spaces here
} else {
assert false : "The key must be target, factor, offset, special, or systems";
}
}
// HERE a single conversion rate data should be loaded
assert (target != null);
assert (factor != null || special != null);
mapToConversionRate.put(simpleUnit, new ConversionRateInfo(simpleUnit, target, factor, offset, special, systems));
}
}
public HashMap getMapToConversionRate() {
return mapToConversionRate;
}
}
public static class ConversionRateInfo {
@SuppressWarnings("unused")
private final String simpleUnit;
private final String target;
private final String conversionRate;
private final BigDecimal offset;
private final String specialMappingName; // the name of a special mapping used instead of factor + optional offset.
private final String systems;
public ConversionRateInfo(String simpleUnit, String target, String conversionRate, String offset, String special, String systems) {
this.simpleUnit = simpleUnit;
this.target = target;
this.conversionRate = conversionRate;
this.offset = forNumberWithDivision(offset);
this.specialMappingName = special;
this.systems = systems;
}
private static BigDecimal forNumberWithDivision(String numberWithDivision) {
String[] numbers = numberWithDivision.split("/");
assert (numbers.length <= 2);
if (numbers.length == 1) {
return new BigDecimal(numbers[0]);
}
return new BigDecimal(numbers[0]).divide(new BigDecimal(numbers[1]), MathContext.DECIMAL128);
}
/**
* @return the base unit.
*
* For example:
* ("meter", "foot", "inch", "mile" ... etc.) have "meter" as a base/root unit.
*/
public String getTarget() {
return this.target;
}
/**
* @return The offset from this unit to the base unit.
*/
public BigDecimal getOffset() {
return this.offset;
}
/**
* @return The conversion rate from this unit to the base unit.
*/
public String getConversionRate() {
if (conversionRate==null) {
throw new IllegalIcuArgumentException("trying to use a null conversion rate (for special?)");
}
return conversionRate;
}
/**
* @return The name of the special conversion system for this unit (used instead of factor + optional offset).
*/
public String getSpecialMappingName() {
return specialMappingName;
}
/**
* @return The measurement systems this unit belongs to.
*/
public String getSystems() {
return systems;
}
}
}