| /* 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.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| |
| import android.icu.impl.ICUData; |
| import android.icu.impl.ICUResourceBundle; |
| import android.icu.impl.IllegalIcuArgumentException; |
| import android.icu.impl.UResource; |
| import android.icu.util.ULocale; |
| import android.icu.util.UResourceBundle; |
| |
| /** |
| * Responsible for all units data operations (retriever, analysis, extraction certain data ... etc.). |
| * @hide Only a subset of ICU is exposed in Android |
| */ |
| public class UnitsData { |
| // TODO(icu-units#122): this class can use static initialization to load the |
| // data once, and provide access to it via static methods. (Partial change |
| // has been done already.) |
| |
| // Array of simple unit IDs. |
| private static String[] simpleUnits = null; |
| |
| // Maps from the value associated with each simple unit ID to a category |
| // index number. |
| private static int[] simpleUnitCategories = null; |
| |
| private ConversionRates conversionRates; |
| private UnitPreferences unitPreferences; |
| |
| |
| public UnitsData() { |
| this.conversionRates = new ConversionRates(); |
| this.unitPreferences = new UnitPreferences(); |
| } |
| |
| public static String[] getSimpleUnits() { |
| return simpleUnits; |
| } |
| |
| static { |
| // 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; |
| simpleUnitCategories = sink.simpleUnitCategories; |
| } |
| |
| public ConversionRates getConversionRates() { |
| return conversionRates; |
| } |
| |
| public UnitPreferences getUnitPreferences() { |
| return unitPreferences; |
| } |
| |
| public static int getCategoryIndexOfSimpleUnit(int simpleUnitIndex) { |
| return simpleUnitCategories[simpleUnitIndex]; |
| } |
| |
| /** |
| * @param measureUnit An instance of MeasureUnitImpl. |
| * @return the corresponding category. |
| */ |
| public String getCategory(MeasureUnitImpl measureUnit) { |
| MeasureUnitImpl baseMeasureUnitImpl |
| = this.getConversionRates().extractCompoundBaseUnit(measureUnit); |
| baseMeasureUnitImpl.serialize(); |
| String identifier = baseMeasureUnitImpl.getIdentifier(); |
| |
| |
| Integer index = Categories.baseUnitToIndex.get(identifier); |
| |
| // In case the base unit identifier did not match any entry. |
| if (index == null) { |
| baseMeasureUnitImpl.takeReciprocal(); |
| baseMeasureUnitImpl.serialize(); |
| identifier = baseMeasureUnitImpl.getIdentifier(); |
| index = Categories.baseUnitToIndex.get(identifier); |
| } |
| |
| // In case the reciprocal of the base unit identifier did not match any entry. |
| baseMeasureUnitImpl.takeReciprocal(); // return to original form |
| MeasureUnitImpl simplifiedUnit = baseMeasureUnitImpl.copyAndSimplify(); |
| if (index == null) { |
| simplifiedUnit.serialize(); |
| identifier = simplifiedUnit.getIdentifier(); |
| index = Categories.baseUnitToIndex.get(identifier); |
| } |
| |
| // In case the simplified base unit identifier did not match any entry. |
| if (index == null) { |
| simplifiedUnit.takeReciprocal(); |
| simplifiedUnit.serialize(); |
| identifier = simplifiedUnit.getIdentifier(); |
| index = Categories.baseUnitToIndex.get(identifier); |
| } |
| |
| // If there is no match at all, throw an exception. |
| if (index == null) { |
| throw new IllegalIcuArgumentException("This unit does not has a category" + measureUnit.getIdentifier()); |
| } |
| |
| return Categories.indexToCategory[index]; |
| } |
| |
| public UnitPreferences.UnitPreference[] getPreferencesFor(String category, String usage, ULocale locale) { |
| return this.unitPreferences.getPreferencesFor(category, usage, locale, this); |
| } |
| |
| /** |
| * @hide Only a subset of ICU is exposed in Android |
| */ |
| public static class SimpleUnitIdentifiersSink extends UResource.Sink { |
| String[] simpleUnits = null; |
| int[] simpleUnitCategories = 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<>(); |
| ArrayList<Integer> simpleUnitCategories = 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; |
| } |
| |
| // Find the base target unit for this simple unit |
| UResource.Table table = value.getTable(); |
| if (!table.findValue("target", value)) { |
| // TODO: is there a more idiomatic way to deal with Resource |
| // Sink data errors in ICU4J? For now we just assert-fail, |
| // and otherwise skip bad data: |
| assert false : "Could not find \"target\" for simple unit: " + key; |
| continue; |
| } |
| String target = value.getString(); |
| |
| simpleUnits.add(key.toString()); |
| simpleUnitCategories.add(Categories.baseUnitToIndex.get(target)); |
| } |
| |
| this.simpleUnits = simpleUnits.toArray(new String[0]); |
| this.simpleUnitCategories = new int[simpleUnitCategories.size()]; |
| Iterator<Integer> iter = simpleUnitCategories.iterator(); |
| for (int i = 0; i < this.simpleUnitCategories.length; i++) |
| { |
| this.simpleUnitCategories[i] = iter.next().intValue(); |
| } |
| } |
| } |
| |
| /** |
| * Contains all the needed constants. |
| * @hide Only a subset of ICU is exposed in Android |
| */ |
| public static class Constants { |
| // TODO: consider moving the Trie-offset-related constants into |
| // MeasureUnitImpl.java, the only place they're being used? |
| |
| // 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 or binary prefixes. This is big enough to |
| // ensure we only insert positive integers into the trie. |
| public static final int kPrefixOffset = 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"; |
| } |
| |
| // Deals with base units and categories, e.g. "meter-per-second" --> "speed". |
| /** |
| * @hide Only a subset of ICU is exposed in Android |
| */ |
| public static class Categories { |
| /** |
| * Maps from base unit to an index value: an index into the |
| * indexToCategory array. |
| */ |
| static HashMap<String, Integer> baseUnitToIndex; |
| |
| /** |
| * Our official array of category strings - categories are identified by |
| * indeces into this array. |
| */ |
| static String[] indexToCategory; |
| |
| static { |
| // 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); |
| baseUnitToIndex = sink.mapFromUnitToIndex; |
| indexToCategory = sink.categories.toArray(new String[0]); |
| } |
| } |
| |
| /** |
| * A Resource Sink that collects information from `unitQuantities` in the |
| * `units` resource to provide key->value lookups from base unit to |
| * category, as well as preserving ordering information for these |
| * categories. See `units.txt`. |
| * |
| * For example: "kilogram" -> "mass", "meter-per-second" -> "speed". |
| * |
| * In Java unitQuantity values are collected in order into an ArrayList, |
| * while unitQuantity key-to-index lookups are handled with a HashMap. |
| * @hide Only a subset of ICU is exposed in Android |
| */ |
| 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, Integer> mapFromUnitToIndex; |
| ArrayList<String> categories; |
| |
| public CategoriesSink() { |
| mapFromUnitToIndex = new HashMap<>(); |
| categories = new ArrayList<>(); |
| } |
| |
| @Override |
| public void put(UResource.Key key, UResource.Value value, boolean noFallback) { |
| assert (key.toString().equals(Constants.CATEGORY_TABLE_NAME)); |
| assert (value.getType() == UResourceBundle.ARRAY); |
| |
| UResource.Array categoryArray = value.getArray(); |
| for (int i=0; categoryArray.getValue(i, value); i++) { |
| assert (value.getType() == UResourceBundle.TABLE); |
| UResource.Table table = value.getTable(); |
| assert (table.getSize() == 1) |
| : "expecting single-entry table, got size: " + table.getSize(); |
| table.getKeyAndValue(0, key, value); |
| assert value.getType() == UResourceBundle.STRING : "expecting category string"; |
| mapFromUnitToIndex.put(key.toString(), categories.size()); |
| categories.add(value.toString()); |
| } |
| } |
| } |
| } |