blob: c3e9108447f11e413f8637162952109571c80624 [file] [log] [blame]
// © 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.util.ArrayList;
import java.util.HashMap;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.UResource;
import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.UResourceBundle;
/**
* Responsible for all units data operations (retriever, analysis, extraction certain data ... etc.).
*/
public class UnitsData {
private volatile static String[] simpleUnits = null;
private ConversionRates conversionRates;
private UnitPreferences unitPreferences;
/**
* Pairs of categories and the corresponding base units.
*/
private Categories categories;
public UnitsData() {
this.conversionRates = new ConversionRates();
this.unitPreferences = new UnitPreferences();
this.categories = new Categories();
}
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 ConversionRates getConversionRates() {
return conversionRates;
}
public UnitPreferences getUnitPreferences() {
return unitPreferences;
}
/**
* @param measureUnit
* @return the corresponding category.
*/
public String getCategory(MeasureUnitImpl measureUnit) {
MeasureUnitImpl baseMeasureUnit
= this.getConversionRates().extractCompoundBaseUnit(measureUnit);
String baseUnitIdentifier = MeasureUnit.fromMeasureUnitImpl(baseMeasureUnit).getIdentifier();
if (baseUnitIdentifier.equals("meter-per-cubic-meter")) {
// TODO(CLDR-13787,hugovdm): special-casing the consumption-inverse
// case. Once CLDR-13787 is clarified, this should be generalised (or
// possibly removed):
return "consumption-inverse";
}
return this.categories.mapFromUnitToCategory.get(baseUnitIdentifier);
}
public UnitPreferences.UnitPreference[] getPreferencesFor(String category, String usage, String region) {
return this.unitPreferences.getPreferencesFor(category, usage, region);
}
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";
}
public static class Categories {
/**
* Contains the map between units in their base units into their category.
* For example: meter-per-second --> "speed"
*/
HashMap<String, String> mapFromUnitToCategory;
public Categories() {
// Read unit Categories
ICUResourceBundle resource;
resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, "units");
CategoriesSink sink = new CategoriesSink();
resource.getAllItemsWithFallback(Constants.CATEGORY_TABLE_NAME, sink);
this.mapFromUnitToCategory = sink.getMapFromUnitToCategory();
}
}
public static class CategoriesSink extends UResource.Sink {
/**
* Contains the map between units in their base units into their category.
* For example: meter-per-second --> "speed"
*/
HashMap<String, String> mapFromUnitToCategory;
public CategoriesSink() {
mapFromUnitToCategory = new HashMap<>();
}
@Override
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
assert (key.toString() == Constants.CATEGORY_TABLE_NAME);
assert (value.getType() == UResourceBundle.TABLE);
UResource.Table categoryTable = value.getTable();
for (int i = 0; categoryTable.getKeyAndValue(i, key, value); i++) {
assert (value.getType() == UResourceBundle.STRING);
mapFromUnitToCategory.put(key.toString(), value.toString());
}
}
public HashMap<String, String> getMapFromUnitToCategory() {
return mapFromUnitToCategory;
}
}
}