1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html#License 3 /* 4 ******************************************************************************* 5 * Copyright (C) 2015-2016, International Business Machines Corporation and 6 * others. All Rights Reserved. 7 ******************************************************************************* 8 */ 9 package com.ibm.icu.impl; 10 11 import java.util.Collections; 12 import java.util.EnumMap; 13 import java.util.HashMap; 14 import java.util.HashSet; 15 import java.util.Map; 16 import java.util.Map.Entry; 17 import java.util.Set; 18 19 import com.ibm.icu.impl.locale.AsciiUtil; 20 import com.ibm.icu.util.UResourceBundle; 21 import com.ibm.icu.util.UResourceBundleIterator; 22 23 /** 24 * @author markdavis 25 * 26 */ 27 public class ValidIdentifiers { 28 29 public enum Datatype { 30 currency, 31 language, 32 region, 33 script, 34 subdivision, 35 unit, 36 variant, 37 u, 38 t, 39 x, 40 illegal 41 } 42 43 public enum Datasubtype { 44 deprecated, 45 private_use, 46 regular, 47 special, 48 unknown, 49 macroregion, 50 } 51 52 public static class ValiditySet { 53 public final Set<String> regularData; 54 public final Map<String,Set<String>> subdivisionData; ValiditySet(Set<String> plainData, boolean makeMap)55 public ValiditySet(Set<String> plainData, boolean makeMap) { 56 if (makeMap) { 57 HashMap<String,Set<String>> _subdivisionData = new HashMap<String,Set<String>>(); 58 for (String s : plainData) { 59 int pos = s.indexOf('-'); // read v28 data also 60 int pos2 = pos+1; 61 if (pos < 0) { 62 pos2 = pos = s.charAt(0) < 'A' ? 3 : 2; 63 } 64 final String key = s.substring(0, pos); 65 final String subdivision = s.substring(pos2); 66 67 Set<String> oldSet = _subdivisionData.get(key); 68 if (oldSet == null) { 69 _subdivisionData.put(key, oldSet = new HashSet<String>()); 70 } 71 oldSet.add(subdivision); 72 } 73 this.regularData = null; 74 HashMap<String,Set<String>> _subdivisionData2 = new HashMap<String,Set<String>>(); 75 // protect the sets 76 for (Entry<String, Set<String>> e : _subdivisionData.entrySet()) { 77 Set<String> value = e.getValue(); 78 // optimize a bit by using singleton 79 Set<String> set = value.size() == 1 ? Collections.singleton(value.iterator().next()) 80 : Collections.unmodifiableSet(value); 81 _subdivisionData2.put(e.getKey(), set); 82 } 83 84 this.subdivisionData = Collections.unmodifiableMap(_subdivisionData2); 85 } else { 86 this.regularData = Collections.unmodifiableSet(plainData); 87 this.subdivisionData = null; 88 } 89 } 90 91 public boolean contains(String code) { 92 if (regularData != null) { 93 return regularData.contains(code); 94 } else { 95 int pos = code.indexOf('-'); 96 String key = code.substring(0,pos); 97 final String value = code.substring(pos+1); 98 return contains(key, value); 99 } 100 } 101 102 public boolean contains(String key, String value) { 103 Set<String> oldSet = subdivisionData.get(key); 104 return oldSet != null && oldSet.contains(value); 105 } 106 107 @Override 108 public String toString() { 109 if (regularData != null) { 110 return regularData.toString(); 111 } else { 112 return subdivisionData.toString(); 113 } 114 } 115 } 116 117 private static class ValidityData { 118 static final Map<Datatype,Map<Datasubtype,ValiditySet>> data; 119 static { 120 Map<Datatype, Map<Datasubtype, ValiditySet>> _data = new EnumMap<Datatype,Map<Datasubtype,ValiditySet>>(Datatype.class); 121 UResourceBundle suppData = UResourceBundle.getBundleInstance( 122 ICUData.ICU_BASE_NAME, 123 "supplementalData", 124 ICUResourceBundle.ICU_DATA_CLASS_LOADER); 125 UResourceBundle validityInfo = suppData.get("idValidity"); 126 for(UResourceBundleIterator datatypeIterator = validityInfo.getIterator(); 127 datatypeIterator.hasNext();) { 128 UResourceBundle datatype = datatypeIterator.next(); 129 String rawKey = datatype.getKey(); 130 Datatype key = Datatype.valueOf(rawKey); 131 Map<Datasubtype,ValiditySet> values = new EnumMap<Datasubtype,ValiditySet>(Datasubtype.class); 132 for(UResourceBundleIterator datasubtypeIterator = datatype.getIterator(); 133 datasubtypeIterator.hasNext();) { 134 UResourceBundle datasubtype = datasubtypeIterator.next(); 135 String rawsubkey = datasubtype.getKey(); 136 Datasubtype subkey = Datasubtype.valueOf(rawsubkey); 137 // handle single value specially 138 Set<String> subvalues = new HashSet<String>(); 139 if (datasubtype.getType() == UResourceBundle.STRING) { 140 addRange(datasubtype.getString(), subvalues); 141 } else { 142 for (String string : datasubtype.getStringArray()) { 143 addRange(string, subvalues); 144 } 145 } 146 values.put(subkey, new ValiditySet(subvalues, key == Datatype.subdivision)); 147 } 148 _data.put(key, Collections.unmodifiableMap(values)); 149 } 150 data = Collections.unmodifiableMap(_data); 151 } 152 private static void addRange(String string, Set<String> subvalues) { 153 string = AsciiUtil.toLowerString(string); 154 int pos = string.indexOf('~'); 155 if (pos < 0) { 156 subvalues.add(string); 157 } else { 158 StringRange.expand(string.substring(0,pos), string.substring(pos+1), false, subvalues); 159 } 160 } 161 } 162 163 public static Map<Datatype, Map<Datasubtype, ValiditySet>> getData() { 164 return ValidityData.data; 165 } 166 167 /** 168 * Returns the Datasubtype containing the code, or null if there is none. 169 */ 170 public static Datasubtype isValid(Datatype datatype, Set<Datasubtype> datasubtypes, String code) { 171 Map<Datasubtype, ValiditySet> subtable = ValidityData.data.get(datatype); 172 if (subtable != null) { 173 for (Datasubtype datasubtype : datasubtypes) { 174 ValiditySet validitySet = subtable.get(datasubtype); 175 if (validitySet != null) { 176 if (validitySet.contains(AsciiUtil.toLowerString(code))) { 177 return datasubtype; 178 } 179 } 180 } 181 } 182 return null; 183 } 184 185 public static Datasubtype isValid(Datatype datatype, Set<Datasubtype> datasubtypes, String code, String value) { 186 Map<Datasubtype, ValiditySet> subtable = ValidityData.data.get(datatype); 187 if (subtable != null) { 188 code = AsciiUtil.toLowerString(code); 189 value = AsciiUtil.toLowerString(value); 190 for (Datasubtype datasubtype : datasubtypes) { 191 ValiditySet validitySet = subtable.get(datasubtype); 192 if (validitySet != null) { 193 if (validitySet.contains(code, value)) { 194 return datasubtype; 195 } 196 } 197 } 198 } 199 return null; 200 } 201 } 202