• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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