Home | History | Annotate | Download | only in impl
      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;
     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