com.ibm.icu.impl.units.UnitPreferences Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of icu4j Show documentation
Show all versions of icu4j Show documentation
International Component for Unicode for Java (ICU4J) is a mature, widely used Java library
providing Unicode and Globalization support
// © 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.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.UResource;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
public class UnitPreferences {
private static final Map measurementSystem;
static {
Map tempMS = new HashMap<>();
tempMS.put("metric", "001");
tempMS.put("ussystem", "US");
tempMS.put("uksystem", "GB");
measurementSystem = Collections.unmodifiableMap(tempMS);
}
private HashMap> mapToUnitPreferences = new HashMap<>();
public UnitPreferences() {
// Read unit preferences
ICUResourceBundle resource;
resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, "units");
UnitPreferencesSink sink = new UnitPreferencesSink();
resource.getAllItemsWithFallback(UnitsData.Constants.UNIT_PREFERENCE_TABLE_NAME, sink);
this.mapToUnitPreferences = sink.getMapToUnitPreferences();
}
public static String formMapKey(String category, String usage) {
return category + "++" + usage;
}
/**
* Extracts all the sub-usages from a usage including the default one in the end.
* The usages will be in order starting with the longest matching one.
* For example:
* if usage : "person-height-child"
* the function will return: "person-height-child"
* "person-height"
* "person"
* "default"
*
* @param usage
* @return
*/
private static String[] getAllUsages(String usage) {
ArrayList result = new ArrayList<>();
result.add(usage);
for (int i = usage.length() - 1; i >= 0; --i) {
if (usage.charAt(i) == '-') {
result.add(usage.substring(0, i));
}
}
if (!usage.equals(UnitsData.Constants.DEFAULT_USAGE)) { // Do not add default usage twice.
result.add(UnitsData.Constants.DEFAULT_USAGE);
}
return result.toArray(new String[0]);
}
public UnitPreference[] getPreferencesFor(String category, String usage, ULocale locale, UnitsData data) {
// TODO: remove this condition when all the categories are allowed.
// WARNING: when this is removed please make sure to keep the "fahrenhe" => "fahrenheit" mapping
if ("temperature".equals(category)) {
String localeUnit = locale.getKeywordValue("mu");
// The value for -u-mu- is `fahrenhe`, but CLDR and everything else uses `fahrenheit`
if ("fahrenhe".equals(localeUnit)) {
localeUnit = "fahrenheit";
}
String localeUnitCategory;
try {
localeUnitCategory = localeUnit == null ? null : data.getCategory(MeasureUnitImpl.forIdentifier(localeUnit));
} catch (Exception e) {
localeUnitCategory = null;
}
if (localeUnitCategory != null && category.equals(localeUnitCategory)) {
UnitPreference[] preferences = {new UnitPreference(localeUnit, null, null)};
return preferences;
}
}
String region = ULocale.getRegionForSupplementalData(locale, false);
// Check the locale system tag, e.g `ms=metric`.
String localeSystem = locale.getKeywordValue("measure");
boolean isLocaleSystem = measurementSystem.containsKey(localeSystem);
String[] subUsages = getAllUsages(usage);
UnitPreference[] result = null;
for (String subUsage :
subUsages) {
result = getUnitPreferences(category, subUsage, region);
if (result != null && isLocaleSystem) {
ConversionRates rates = new ConversionRates();
boolean unitsMatchSystem = true;
for (UnitPreference unitPref : result) {
MeasureUnitImpl measureUnit = MeasureUnitImpl.forIdentifier(unitPref.getUnit());
List singleUnits = new ArrayList<>(measureUnit.getSingleUnits());
for (SingleUnitImpl singleUnit : singleUnits) {
String systems = rates.extractSystems(singleUnit);
if (!systems.contains("metric_adjacent")) {
if (!systems.contains(localeSystem)) {
unitsMatchSystem = false;
}
}
}
}
if (!unitsMatchSystem) {
String newRegion = measurementSystem.get(localeSystem);
result = getUnitPreferences(category, subUsage, newRegion);
}
}
if (result != null) break;
}
// TODO: if a category is missing, we get an assertion failure, or we
// return null, causing a NullPointerException. In C++, we return an
// U_MISSING_RESOURCE_ERROR error.
assert (result != null) : "At least the category must be exist";
return result;
}
/**
* @param category
* @param usage
* @param region
* @return null if there is no entry associated to the category and usage. O.W. returns the corresponding UnitPreference[]
*/
private UnitPreference[] getUnitPreferences(String category, String usage, String region) {
String key = formMapKey(category, usage);
if (this.mapToUnitPreferences.containsKey(key)) {
HashMap unitPreferencesMap = this.mapToUnitPreferences.get(key);
UnitPreference[] result =
unitPreferencesMap.containsKey(region) ?
unitPreferencesMap.get(region) :
unitPreferencesMap.get(UnitsData.Constants.DEFAULT_REGION);
assert (result != null);
return result;
}
return null;
}
public static class UnitPreference {
private final String unit;
private final BigDecimal geq;
private final String skeleton;
public UnitPreference(String unit, String geq, String skeleton) {
this.unit = unit;
this.geq = geq == null ? BigDecimal.valueOf( Double.MIN_VALUE) /* -inf */ : new BigDecimal(geq);
this.skeleton = skeleton == null? "" : skeleton;
}
public String getUnit() {
return this.unit;
}
public BigDecimal getGeq() {
return geq;
}
public String getSkeleton() {
return skeleton;
}
}
public static class UnitPreferencesSink extends UResource.Sink {
private HashMap> mapToUnitPreferences;
public UnitPreferencesSink() {
this.mapToUnitPreferences = new HashMap<>();
}
public HashMap> getMapToUnitPreferences() {
return mapToUnitPreferences;
}
/**
* The unitPreferenceData structure (see icu4c/source/data/misc/units.txt) contains a
* hierarchy of category/usage/region, within which are a set of
* preferences. Hence three for-loops and another loop for the
* preferences themselves.
*/
@Override
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
assert (UnitsData.Constants.UNIT_PREFERENCE_TABLE_NAME.equals(key.toString()));
UResource.Table categoryTable = value.getTable();
for (int i = 0; categoryTable.getKeyAndValue(i, key, value); i++) {
assert (value.getType() == UResourceBundle.TABLE);
String category = key.toString();
UResource.Table usageTable = value.getTable();
for (int j = 0; usageTable.getKeyAndValue(j, key, value); j++) {
assert (value.getType() == UResourceBundle.TABLE);
String usage = key.toString();
UResource.Table regionTable = value.getTable();
for (int k = 0; regionTable.getKeyAndValue(k, key, value); k++) {
assert (value.getType() == UResourceBundle.ARRAY);
String region = key.toString();
UResource.Array preferencesTable = value.getArray();
ArrayList unitPreferences = new ArrayList<>();
for (int l = 0; preferencesTable.getValue(l, value); l++) {
assert (value.getType() == UResourceBundle.TABLE);
UResource.Table singlePrefTable = value.getTable();
// TODO collect the data
String unit = null;
String geq = "1";
String skeleton = "";
for (int m = 0; singlePrefTable.getKeyAndValue(m, key, value); m++) {
assert (value.getType() == UResourceBundle.STRING);
String keyString = key.toString();
if ("unit".equals(keyString)) {
unit = value.getString();
} else if ("geq".equals(keyString)) {
geq = value.getString();
} else if ("skeleton".equals(keyString)) {
skeleton = value.getString();
} else {
assert false : "key must be unit, geq or skeleton";
}
}
assert (unit != null);
unitPreferences.add(new UnitPreference(unit, geq, skeleton));
}
assert (!unitPreferences.isEmpty());
this.insertUnitPreferences(
category,
usage,
region,
unitPreferences.toArray(new UnitPreference[0])
);
}
}
}
}
private void insertUnitPreferences(String category, String usage, String region, UnitPreference[] unitPreferences) {
String key = formMapKey(category, usage);
HashMap shouldInsert;
if (this.mapToUnitPreferences.containsKey(key)) {
shouldInsert = this.mapToUnitPreferences.get(key);
} else {
shouldInsert = new HashMap<>();
this.mapToUnitPreferences.put(key, shouldInsert);
}
shouldInsert.put(region, unitPreferences);
}
}
}