1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 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 reserved, 51 } 52 53 public static class ValiditySet { 54 public final Set<String> regularData; 55 public final Map<String,Set<String>> subdivisionData; ValiditySet(Set<String> plainData, boolean makeMap)56 public ValiditySet(Set<String> plainData, boolean makeMap) { 57 if (makeMap) { 58 HashMap<String,Set<String>> _subdivisionData = new HashMap<String,Set<String>>(); 59 for (String s : plainData) { 60 int pos = s.indexOf('-'); // read v28 data also 61 int pos2 = pos+1; 62 if (pos < 0) { 63 pos2 = pos = s.charAt(0) < 'A' ? 3 : 2; 64 } 65 final String key = s.substring(0, pos); 66 final String subdivision = s.substring(pos2); 67 68 Set<String> oldSet = _subdivisionData.get(key); 69 if (oldSet == null) { 70 _subdivisionData.put(key, oldSet = new HashSet<String>()); 71 } 72 oldSet.add(subdivision); 73 } 74 this.regularData = null; 75 HashMap<String,Set<String>> _subdivisionData2 = new HashMap<String,Set<String>>(); 76 // protect the sets 77 for (Entry<String, Set<String>> e : _subdivisionData.entrySet()) { 78 Set<String> value = e.getValue(); 79 // optimize a bit by using singleton 80 Set<String> set = value.size() == 1 ? Collections.singleton(value.iterator().next()) 81 : Collections.unmodifiableSet(value); 82 _subdivisionData2.put(e.getKey(), set); 83 } 84 85 this.subdivisionData = Collections.unmodifiableMap(_subdivisionData2); 86 } else { 87 this.regularData = Collections.unmodifiableSet(plainData); 88 this.subdivisionData = null; 89 } 90 } 91 92 public boolean contains(String code) { 93 if (regularData != null) { 94 return regularData.contains(code); 95 } else { 96 int pos = code.indexOf('-'); 97 String key = code.substring(0,pos); 98 final String value = code.substring(pos+1); 99 return contains(key, value); 100 } 101 } 102 103 public boolean contains(String key, String value) { 104 Set<String> oldSet = subdivisionData.get(key); 105 return oldSet != null && oldSet.contains(value); 106 } 107 108 @Override 109 public String toString() { 110 if (regularData != null) { 111 return regularData.toString(); 112 } else { 113 return subdivisionData.toString(); 114 } 115 } 116 } 117 118 private static class ValidityData { 119 static final Map<Datatype,Map<Datasubtype,ValiditySet>> data; 120 static { 121 Map<Datatype, Map<Datasubtype, ValiditySet>> _data = new EnumMap<Datatype,Map<Datasubtype,ValiditySet>>(Datatype.class); 122 UResourceBundle suppData = UResourceBundle.getBundleInstance( 123 ICUData.ICU_BASE_NAME, 124 "supplementalData", 125 ICUResourceBundle.ICU_DATA_CLASS_LOADER); 126 UResourceBundle validityInfo = suppData.get("idValidity"); 127 for(UResourceBundleIterator datatypeIterator = validityInfo.getIterator(); 128 datatypeIterator.hasNext();) { 129 UResourceBundle datatype = datatypeIterator.next(); 130 String rawKey = datatype.getKey(); 131 Datatype key = Datatype.valueOf(rawKey); 132 Map<Datasubtype,ValiditySet> values = new EnumMap<Datasubtype,ValiditySet>(Datasubtype.class); 133 for(UResourceBundleIterator datasubtypeIterator = datatype.getIterator(); 134 datasubtypeIterator.hasNext();) { 135 UResourceBundle datasubtype = datasubtypeIterator.next(); 136 String rawsubkey = datasubtype.getKey(); 137 Datasubtype subkey = Datasubtype.valueOf(rawsubkey); 138 // handle single value specially 139 Set<String> subvalues = new HashSet<String>(); 140 if (datasubtype.getType() == UResourceBundle.STRING) { 141 addRange(datasubtype.getString(), subvalues); 142 } else { 143 for (String string : datasubtype.getStringArray()) { 144 addRange(string, subvalues); 145 } 146 } 147 values.put(subkey, new ValiditySet(subvalues, key == Datatype.subdivision)); 148 } 149 _data.put(key, Collections.unmodifiableMap(values)); 150 } 151 data = Collections.unmodifiableMap(_data); 152 } 153 private static void addRange(String string, Set<String> subvalues) { 154 string = AsciiUtil.toLowerString(string); 155 int pos = string.indexOf('~'); 156 if (pos < 0) { 157 subvalues.add(string); 158 } else { 159 StringRange.expand(string.substring(0,pos), string.substring(pos+1), false, subvalues); 160 } 161 } 162 } 163 164 public static Map<Datatype, Map<Datasubtype, ValiditySet>> getData() { 165 return ValidityData.data; 166 } 167 168 /** 169 * Returns the Datasubtype containing the code, or null if there is none. 170 */ 171 public static Datasubtype isValid(Datatype datatype, Set<Datasubtype> datasubtypes, String code) { 172 Map<Datasubtype, ValiditySet> subtable = ValidityData.data.get(datatype); 173 if (subtable != null) { 174 for (Datasubtype datasubtype : datasubtypes) { 175 ValiditySet validitySet = subtable.get(datasubtype); 176 if (validitySet != null) { 177 if (validitySet.contains(AsciiUtil.toLowerString(code))) { 178 return datasubtype; 179 } 180 } 181 } 182 } 183 return null; 184 } 185 186 public static Datasubtype isValid(Datatype datatype, Set<Datasubtype> datasubtypes, String code, String value) { 187 Map<Datasubtype, ValiditySet> subtable = ValidityData.data.get(datatype); 188 if (subtable != null) { 189 code = AsciiUtil.toLowerString(code); 190 value = AsciiUtil.toLowerString(value); 191 for (Datasubtype datasubtype : datasubtypes) { 192 ValiditySet validitySet = subtable.get(datasubtype); 193 if (validitySet != null) { 194 if (validitySet.contains(code, value)) { 195 return datasubtype; 196 } 197 } 198 } 199 } 200 return null; 201 } 202 } 203