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