1 // © 2020 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 package com.ibm.icu.impl.units; 4 5 import java.math.BigDecimal; 6 import java.util.ArrayList; 7 import java.util.HashMap; 8 9 import com.ibm.icu.impl.ICUData; 10 import com.ibm.icu.impl.ICUResourceBundle; 11 import com.ibm.icu.impl.UResource; 12 import com.ibm.icu.util.UResourceBundle; 13 14 public class UnitPreferences { 15 16 private HashMap<String, HashMap<String, UnitPreference[]>> mapToUnitPreferences = new HashMap<>(); 17 UnitPreferences()18 public UnitPreferences() { 19 // Read unit preferences 20 ICUResourceBundle resource; 21 resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, "units"); 22 UnitPreferencesSink sink = new UnitPreferencesSink(); 23 resource.getAllItemsWithFallback(UnitsData.Constants.UNIT_PREFERENCE_TABLE_NAME, sink); 24 this.mapToUnitPreferences = sink.getMapToUnitPreferences(); 25 } 26 formMapKey(String category, String usage)27 public static String formMapKey(String category, String usage) { 28 return category + "++" + usage; 29 } 30 31 /** 32 * Extracts all the sub-usages from a usage including the default one in the end. 33 * The usages will be in order starting with the longest matching one. 34 * For example: 35 * if usage : "person-height-child" 36 * the function will return: "person-height-child" 37 * "person-height" 38 * "person" 39 * "default" 40 * 41 * @param usage 42 * @return 43 */ getAllUsages(String usage)44 private static String[] getAllUsages(String usage) { 45 ArrayList<String> result = new ArrayList<>(); 46 result.add(usage); 47 for (int i = usage.length() - 1; i >= 0; --i) { 48 if (usage.charAt(i) == '-') { 49 result.add(usage.substring(0, i)); 50 } 51 } 52 53 if (!usage.equals(UnitsData.Constants.DEFAULT_USAGE)) { // Do not add default usage twice. 54 result.add(UnitsData.Constants.DEFAULT_USAGE); 55 } 56 return result.toArray(new String[0]); 57 } 58 getPreferencesFor(String category, String usage, String region)59 public UnitPreference[] getPreferencesFor(String category, String usage, String region) { 60 String[] subUsages = getAllUsages(usage); 61 UnitPreference[] result = null; 62 for (String subUsage : 63 subUsages) { 64 result = getUnitPreferences(category, subUsage, region); 65 if (result != null) break; 66 } 67 // TODO: if a category is missing, we get an assertion failure, or we 68 // return null, causing a NullPointerException. In C++, we return an 69 // U_MISSING_RESOURCE_ERROR error. 70 assert (result != null) : "At least the category must be exist"; 71 return result; 72 } 73 74 /** 75 * @param category 76 * @param usage 77 * @param region 78 * @return null if there is no entry associated to the category and usage. O.W. returns the corresponding UnitPreference[] 79 */ getUnitPreferences(String category, String usage, String region)80 private UnitPreference[] getUnitPreferences(String category, String usage, String region) { 81 String key = formMapKey(category, usage); 82 if (this.mapToUnitPreferences.containsKey(key)) { 83 HashMap<String, UnitPreference[]> unitPreferencesMap = this.mapToUnitPreferences.get(key); 84 UnitPreference[] result = 85 unitPreferencesMap.containsKey(region) ? 86 unitPreferencesMap.get(region) : 87 unitPreferencesMap.get(UnitsData.Constants.DEFAULT_REGION); 88 89 assert (result != null); 90 return result; 91 } 92 93 return null; 94 } 95 96 public static class UnitPreference { 97 private final String unit; 98 private final BigDecimal geq; 99 private final String skeleton; 100 101 UnitPreference(String unit, String geq, String skeleton)102 public UnitPreference(String unit, String geq, String skeleton) { 103 this.unit = unit; 104 this.geq = new BigDecimal(geq); 105 this.skeleton = skeleton; 106 } 107 getUnit()108 public String getUnit() { 109 return this.unit; 110 } 111 getGeq()112 public BigDecimal getGeq() { 113 return geq; 114 } 115 getSkeleton()116 public String getSkeleton() { 117 return skeleton; 118 } 119 } 120 121 public static class UnitPreferencesSink extends UResource.Sink { 122 123 private HashMap<String, HashMap<String, UnitPreference[]>> mapToUnitPreferences; 124 UnitPreferencesSink()125 public UnitPreferencesSink() { 126 this.mapToUnitPreferences = new HashMap<>(); 127 } 128 getMapToUnitPreferences()129 public HashMap<String, HashMap<String, UnitPreference[]>> getMapToUnitPreferences() { 130 return mapToUnitPreferences; 131 } 132 133 /** 134 * The unitPreferenceData structure (see icu4c/source/data/misc/units.txt) contains a 135 * hierarchy of category/usage/region, within which are a set of 136 * preferences. Hence three for-loops and another loop for the 137 * preferences themselves. 138 */ 139 @Override put(UResource.Key key, UResource.Value value, boolean noFallback)140 public void put(UResource.Key key, UResource.Value value, boolean noFallback) { 141 assert (UnitsData.Constants.UNIT_PREFERENCE_TABLE_NAME.equals(key.toString())); 142 143 UResource.Table categoryTable = value.getTable(); 144 for (int i = 0; categoryTable.getKeyAndValue(i, key, value); i++) { 145 assert (value.getType() == UResourceBundle.TABLE); 146 147 String category = key.toString(); 148 UResource.Table usageTable = value.getTable(); 149 for (int j = 0; usageTable.getKeyAndValue(j, key, value); j++) { 150 assert (value.getType() == UResourceBundle.TABLE); 151 152 String usage = key.toString(); 153 UResource.Table regionTable = value.getTable(); 154 for (int k = 0; regionTable.getKeyAndValue(k, key, value); k++) { 155 assert (value.getType() == UResourceBundle.ARRAY); 156 157 String region = key.toString(); 158 UResource.Array preferencesTable = value.getArray(); 159 ArrayList<UnitPreference> unitPreferences = new ArrayList<>(); 160 for (int l = 0; preferencesTable.getValue(l, value); l++) { 161 assert (value.getType() == UResourceBundle.TABLE); 162 163 UResource.Table singlePrefTable = value.getTable(); 164 // TODO collect the data 165 String unit = null; 166 String geq = "1"; 167 String skeleton = ""; 168 for (int m = 0; singlePrefTable.getKeyAndValue(m, key, value); m++) { 169 assert (value.getType() == UResourceBundle.STRING); 170 String keyString = key.toString(); 171 if ("unit".equals(keyString)) { 172 unit = value.getString(); 173 } else if ("geq".equals(keyString)) { 174 geq = value.getString(); 175 } else if ("skeleton".equals(keyString)) { 176 skeleton = value.getString(); 177 } else { 178 assert false : "key must be unit, geq or skeleton"; 179 } 180 } 181 assert (unit != null); 182 unitPreferences.add(new UnitPreference(unit, geq, skeleton)); 183 } 184 185 assert (!unitPreferences.isEmpty()); 186 this.insertUnitPreferences( 187 category, 188 usage, 189 region, 190 unitPreferences.toArray(new UnitPreference[0]) 191 ); 192 } 193 } 194 } 195 } 196 insertUnitPreferences(String category, String usage, String region, UnitPreference[] unitPreferences)197 private void insertUnitPreferences(String category, String usage, String region, UnitPreference[] unitPreferences) { 198 String key = formMapKey(category, usage); 199 HashMap<String, UnitPreference[]> shouldInsert; 200 if (this.mapToUnitPreferences.containsKey(key)) { 201 shouldInsert = this.mapToUnitPreferences.get(key); 202 } else { 203 shouldInsert = new HashMap<>(); 204 this.mapToUnitPreferences.put(key, shouldInsert); 205 } 206 207 shouldInsert.put(region, unitPreferences); 208 } 209 } 210 } 211