Beispiel #1
0
 public static bool IsKey(string s)
 {
     // 2alphanum
     return((s.Length == 2) && AsciiUtil.IsAlphaNumeric(s));
 }
 internal CaseInsensitiveChar(char c)
 {
     _c    = c;
     _hash = AsciiUtil.ToLower(_c);// ICU4N specific - cache hash code, since this is an immutable object
 }
Beispiel #3
0
        private static void InitFromResourceBundle()
        {
            UResourceBundle keyTypeDataRes = UResourceBundle.GetBundleInstance(
                ICUData.ICU_BASE_NAME,
                "keyTypeData",
                ICUResourceBundle.ICU_DATA_CLASS_LOADER);

            GetKeyInfo(keyTypeDataRes.Get("keyInfo"));
            GetTypeInfo(keyTypeDataRes.Get("typeInfo"));

            UResourceBundle keyMapRes  = keyTypeDataRes.Get("keyMap");
            UResourceBundle typeMapRes = keyTypeDataRes.Get("typeMap");

            // alias data is optional
            UResourceBundle typeAliasRes    = null;
            UResourceBundle bcpTypeAliasRes = null;

            try
            {
                typeAliasRes = keyTypeDataRes.Get("typeAlias");
            }
            catch (MissingManifestResourceException e)
            {
                // fall through
            }

            try
            {
                bcpTypeAliasRes = keyTypeDataRes.Get("bcpTypeAlias");
            }
            catch (MissingManifestResourceException e)
            {
                // fall through
            }

            // iterate through keyMap resource
            using (UResourceBundleEnumerator keyMapItr = keyMapRes.GetEnumerator())
            {
                IDictionary <string, IList <string> > _Bcp47Keys = new Dictionary <string, IList <string> >(); // ICU4N NOTE: As long as we don't delete, Dictionary keeps insertion order the same as LinkedHashMap

                while (keyMapItr.MoveNext())
                {
                    UResourceBundle keyMapEntry = keyMapItr.Current;
                    string          legacyKeyId = keyMapEntry.Key;
                    string          bcpKeyId    = keyMapEntry.GetString();

                    bool hasSameKey = false;
                    if (bcpKeyId.Length == 0)
                    {
                        // Empty value indicates that BCP key is same with the legacy key.
                        bcpKeyId   = legacyKeyId;
                        hasSameKey = true;
                    }
                    IList <string> _bcp47Types = new List <string>(); // ICU4N: Mimic LinkedHashSet with List by ensuring no duplicates are added
                    _Bcp47Keys[bcpKeyId] = _bcp47Types.ToUnmodifiableList();

                    bool isTZ = legacyKeyId.Equals("timezone");

                    // reverse type alias map
                    IDictionary <string, ISet <string> > typeAliasMap = null;
                    if (typeAliasRes != null)
                    {
                        UResourceBundle typeAliasResByKey = null;
                        try
                        {
                            typeAliasResByKey = typeAliasRes.Get(legacyKeyId);
                        }
                        catch (MissingManifestResourceException e)
                        {
                            // fall through
                        }
                        if (typeAliasResByKey != null)
                        {
                            typeAliasMap = new Dictionary <string, ISet <string> >();
                            using (UResourceBundleEnumerator typeAliasResItr = typeAliasResByKey.GetEnumerator())
                            {
                                while (typeAliasResItr.MoveNext())
                                {
                                    UResourceBundle typeAliasDataEntry = typeAliasResItr.Current;
                                    string          from = typeAliasDataEntry.Key;
                                    string          to   = typeAliasDataEntry.GetString();
                                    if (isTZ)
                                    {
                                        from = from.Replace(':', '/');
                                    }
                                    ISet <string> aliasSet = typeAliasMap.Get(to);
                                    if (aliasSet == null)
                                    {
                                        aliasSet         = new HashSet <string>();
                                        typeAliasMap[to] = aliasSet;
                                    }
                                    aliasSet.Add(from);
                                }
                            }
                        }
                    }

                    // reverse bcp type alias map
                    IDictionary <string, ISet <string> > bcpTypeAliasMap = null;
                    if (bcpTypeAliasRes != null)
                    {
                        UResourceBundle bcpTypeAliasResByKey = null;
                        try
                        {
                            bcpTypeAliasResByKey = bcpTypeAliasRes.Get(bcpKeyId);
                        }
                        catch (MissingManifestResourceException e)
                        {
                            // fall through
                        }
                        if (bcpTypeAliasResByKey != null)
                        {
                            bcpTypeAliasMap = new Dictionary <string, ISet <string> >();
                            using (UResourceBundleEnumerator bcpTypeAliasResItr = bcpTypeAliasResByKey.GetEnumerator())
                            {
                                while (bcpTypeAliasResItr.MoveNext())
                                {
                                    UResourceBundle bcpTypeAliasDataEntry = bcpTypeAliasResItr.Current;
                                    string          from     = bcpTypeAliasDataEntry.Key;
                                    string          to       = bcpTypeAliasDataEntry.GetString();
                                    ISet <string>   aliasSet = bcpTypeAliasMap.Get(to);
                                    if (aliasSet == null)
                                    {
                                        aliasSet            = new HashSet <string>();
                                        bcpTypeAliasMap[to] = aliasSet;
                                    }
                                    aliasSet.Add(from);
                                }
                            }
                        }
                    }

                    IDictionary <string, Type> typeDataMap    = new Dictionary <string, Type>();
                    ISet <SpecialType>         specialTypeSet = null;

                    // look up type map for the key, and walk through the mapping data
                    UResourceBundle typeMapResByKey = null;
                    try
                    {
                        typeMapResByKey = typeMapRes.Get(legacyKeyId);
                    }
                    catch (MissingManifestResourceException e)
                    {
                        // type map for each key must exist
                        Debug.Assert(false);
                    }
                    if (typeMapResByKey != null)
                    {
                        using (UResourceBundleEnumerator typeMapResByKeyItr = typeMapResByKey.GetEnumerator())
                            while (typeMapResByKeyItr.MoveNext())
                            {
                                UResourceBundle typeMapEntry = typeMapResByKeyItr.Current;
                                string          legacyTypeId = typeMapEntry.Key;
                                string          bcpTypeId    = typeMapEntry.GetString();

                                // special types
                                char first         = legacyTypeId[0];
                                bool isSpecialType = '9' < first && first < 'a' && bcpTypeId.Length == 0;
                                if (isSpecialType)
                                {
                                    if (specialTypeSet == null)
                                    {
                                        specialTypeSet = new HashSet <SpecialType>();
                                    }
                                    specialTypeSet.Add((SpecialType)Enum.Parse(typeof(SpecialType), legacyTypeId, true));
                                    if (!_bcp47Types.Contains(legacyTypeId)) // ICU4N: Mimic LinkedHashSet with a List by not allowing duplicates
                                    {
                                        _bcp47Types.Add(legacyTypeId);
                                    }
                                    continue;
                                }

                                if (isTZ)
                                {
                                    // a timezone key uses a colon instead of a slash in the resource.
                                    // e.g. America:Los_Angeles
                                    legacyTypeId = legacyTypeId.Replace(':', '/');
                                }

                                bool hasSameType = false;
                                if (bcpTypeId.Length == 0)
                                {
                                    // Empty value indicates that BCP type is same with the legacy type.
                                    bcpTypeId   = legacyTypeId;
                                    hasSameType = true;
                                }
                                if (!_bcp47Types.Contains(legacyTypeId)) // ICU4N: Mimic LinkedHashSet with a List by not allowing duplicates
                                {
                                    _bcp47Types.Add(bcpTypeId);
                                }

                                // Note: legacy type value should never be
                                // equivalent to bcp type value of a different
                                // type under the same key. So we use a single
                                // map for lookup.
                                Type t = new Type(legacyTypeId, bcpTypeId);
                                typeDataMap[AsciiUtil.ToLower(legacyTypeId)] = t;
                                if (!hasSameType)
                                {
                                    typeDataMap[AsciiUtil.ToLower(bcpTypeId)] = t;
                                }

                                // Also put aliases in the map
                                if (typeAliasMap != null)
                                {
                                    ISet <string> typeAliasSet = typeAliasMap.Get(legacyTypeId);
                                    if (typeAliasSet != null)
                                    {
                                        foreach (string alias in typeAliasSet)
                                        {
                                            typeDataMap[AsciiUtil.ToLower(alias)] = t;
                                        }
                                    }
                                }
                                if (bcpTypeAliasMap != null)
                                {
                                    ISet <string> bcpTypeAliasSet = bcpTypeAliasMap.Get(bcpTypeId);
                                    if (bcpTypeAliasSet != null)
                                    {
                                        foreach (string alias in bcpTypeAliasSet)
                                        {
                                            typeDataMap[AsciiUtil.ToLower(alias)] = t;
                                        }
                                    }
                                }
                            }
                    }

                    KeyData keyData = new KeyData(legacyKeyId, bcpKeyId, typeDataMap, specialTypeSet);

                    KEYMAP[AsciiUtil.ToLower(legacyKeyId)] = keyData;
                    if (!hasSameKey)
                    {
                        KEYMAP[AsciiUtil.ToLower(bcpKeyId)] = keyData;
                    }
                }

                BCP47_KEYS = _Bcp47Keys.ToUnmodifiableDictionary();
            }
        }
Beispiel #4
0
        private static void InitFromTables()
        {
            foreach (object[] keyDataEntry in KEY_DATA)
            {
                string     legacyKeyId      = (string)keyDataEntry[0];
                string     bcpKeyId         = (string)keyDataEntry[1];
                string[][] typeData         = (string[][])keyDataEntry[2];
                string[][] typeAliasData    = (string[][])keyDataEntry[3];
                string[][] bcpTypeAliasData = (string[][])keyDataEntry[4];

                bool hasSameKey = false;
                if (bcpKeyId == null)
                {
                    bcpKeyId   = legacyKeyId;
                    hasSameKey = true;
                }

                // reverse type alias map
                IDictionary <string, ISet <string> > typeAliasMap = null;
                if (typeAliasData != null)
                {
                    typeAliasMap = new Dictionary <string, ISet <string> >();
                    foreach (string[] typeAliasDataEntry in typeAliasData)
                    {
                        string        from = typeAliasDataEntry[0];
                        string        to   = typeAliasDataEntry[1];
                        ISet <string> aliasSet;
                        if (!typeAliasMap.TryGetValue(to, out aliasSet) || aliasSet == null)
                        {
                            aliasSet         = new HashSet <string>();
                            typeAliasMap[to] = aliasSet;
                        }
                        aliasSet.Add(from);
                    }
                }

                // BCP type alias map data
                IDictionary <string, ISet <string> > bcpTypeAliasMap = null;
                if (bcpTypeAliasData != null)
                {
                    bcpTypeAliasMap = new Dictionary <string, ISet <string> >();
                    foreach (string[] bcpTypeAliasDataEntry in bcpTypeAliasData)
                    {
                        string        from = bcpTypeAliasDataEntry[0];
                        string        to   = bcpTypeAliasDataEntry[1];
                        ISet <string> aliasSet;
                        if (!bcpTypeAliasMap.TryGetValue(to, out aliasSet) || aliasSet == null)
                        {
                            aliasSet            = new HashSet <string>();
                            bcpTypeAliasMap[to] = aliasSet;
                        }
                        aliasSet.Add(from);
                    }
                }

                // Type map data
                Debug.Assert(typeData != null);
                IDictionary <string, Type> typeDataMap    = new Dictionary <string, Type>();
                ISet <SpecialType>         specialTypeSet = null;

                foreach (string[] typeDataEntry in typeData)
                {
                    string legacyTypeId = typeDataEntry[0];
                    string bcpTypeId    = typeDataEntry[1];

                    // special types
                    bool isSpecialType = false;
                    foreach (SpecialType st in Enum.GetValues(typeof(SpecialType)))
                    {
                        if (legacyTypeId.Equals(st.ToString()))
                        {
                            isSpecialType = true;
                            if (specialTypeSet == null)
                            {
                                specialTypeSet = new HashSet <SpecialType>();
                            }
                            specialTypeSet.Add(st);
                            break;
                        }
                    }
                    if (isSpecialType)
                    {
                        continue;
                    }

                    bool hasSameType = false;
                    if (bcpTypeId == null)
                    {
                        bcpTypeId   = legacyTypeId;
                        hasSameType = true;
                    }

                    // Note: legacy type value should never be
                    // equivalent to bcp type value of a different
                    // type under the same key. So we use a single
                    // map for lookup.
                    Type t = new Type(legacyTypeId, bcpTypeId);
                    typeDataMap[AsciiUtil.ToLower(legacyTypeId)] = t;
                    if (!hasSameType)
                    {
                        typeDataMap[AsciiUtil.ToLower(bcpTypeId)] = t;
                    }

                    // Also put aliases in the index
                    ISet <string> typeAliasSet;
                    if (typeAliasMap.TryGetValue(legacyTypeId, out typeAliasSet) && typeAliasSet != null)
                    {
                        foreach (string alias in typeAliasSet)
                        {
                            typeDataMap[AsciiUtil.ToLower(alias)] = t;
                        }
                    }
                    ISet <string> bcpTypeAliasSet;
                    if (!bcpTypeAliasMap.TryGetValue(bcpTypeId, out bcpTypeAliasSet) && bcpTypeAliasSet != null)
                    {
                        foreach (string alias in bcpTypeAliasSet)
                        {
                            typeDataMap[AsciiUtil.ToLower(alias)] = t;
                        }
                    }
                }

                ISet <SpecialType> specialTypes = null;
                if (specialTypeSet != null)
                {
                    specialTypes = new HashSet <SpecialType>(specialTypeSet);
                }

                KeyData keyData = new KeyData(legacyKeyId, bcpKeyId, typeDataMap, specialTypes);

                KEYMAP[AsciiUtil.ToLower(legacyKeyId)] = keyData;
                if (!hasSameKey)
                {
                    KEYMAP[AsciiUtil.ToLower(bcpKeyId)] = keyData;
                }
            }
        }
Beispiel #5
0
 public static string CanonicalizeRegion(string s)
 {
     return(AsciiUtil.ToUpperString(s));
 }
Beispiel #6
0
 public static string CanonicalizeExtensionSubtag(string s)
 {
     return(AsciiUtil.ToLowerString(s));
 }
Beispiel #7
0
 public override int GetHashCode()
 {
     return(AsciiUtil.ToLowerString(_s).GetHashCode());
 }
Beispiel #8
0
 public static string CanonicalizeExtlang(string s)
 {
     return(AsciiUtil.ToLowerString(s));
 }
Beispiel #9
0
 public virtual Extension GetExtension(char key)
 {
     _map.TryGetValue(AsciiUtil.ToLower(key), out Extension result);
     return(result);
 }
Beispiel #10
0
        /// <summary>
        /// Internal constructor, only used by <see cref="InternalLocaleBuilder"/>.
        /// </summary>
        internal LocaleExtensions(IDictionary <CaseInsensitiveChar, string> extensions,
                                  ISet <CaseInsensitiveString> uattributes, IDictionary <CaseInsensitiveString, string> ukeywords)
        {
            bool hasExtension   = (extensions != null && extensions.Count > 0);
            bool hasUAttributes = (uattributes != null && uattributes.Count > 0);
            bool hasUKeywords   = (ukeywords != null && ukeywords.Count > 0);

            if (!hasExtension && !hasUAttributes && !hasUKeywords)
            {
                _map = EmptyMap;
                _id  = "";
                return;
            }

            // Build extension map
            _map = new JCG.SortedDictionary <char, Extension>();
            if (hasExtension)
            {
                foreach (var ext in extensions)
                {
                    char   key   = AsciiUtil.ToLower(ext.Key.Value);
                    string value = ext.Value;

                    if (LanguageTag.IsPrivateusePrefixChar(key))
                    {
                        // we need to exclude special variant in privuateuse, e.g. "x-abc-lvariant-DEF"
                        value = InternalLocaleBuilder.RemovePrivateuseVariant(value);
                        if (value == null)
                        {
                            continue;
                        }
                    }

                    Extension e = new Extension(key, AsciiUtil.ToLower(value));
                    _map[key] = e;
                }
            }

            if (hasUAttributes || hasUKeywords)
            {
                JCG.SortedSet <string> uaset = null;
                JCG.SortedDictionary <string, string> ukmap = null;

                if (hasUAttributes)
                {
                    uaset = new JCG.SortedSet <string>(StringComparer.Ordinal);
                    foreach (CaseInsensitiveString cis in uattributes)
                    {
                        uaset.Add(AsciiUtil.ToLower(cis.Value));
                    }
                }

                if (hasUKeywords)
                {
                    ukmap = new JCG.SortedDictionary <string, string>(StringComparer.Ordinal);
                    foreach (var kwd in ukeywords)
                    {
                        string key  = AsciiUtil.ToLower(kwd.Key.Value);
                        string type = AsciiUtil.ToLower(kwd.Value);
                        ukmap[key] = type;
                    }
                }

                UnicodeLocaleExtension ule = new UnicodeLocaleExtension(uaset, ukmap);
                _map[UnicodeLocaleExtension.Singleton] = ule;
            }

            if (_map.Count == 0)
            {
                // this could happen when only privuateuse with special variant
                _map = EmptyMap;
                _id  = "";
            }
            else
            {
                _id = ToID(_map);
            }
        }
Beispiel #11
0
 public static string CanonicalizeExtensionSingleton(string s)
 {
     return(AsciiUtil.ToLower(s));
 }
Beispiel #12
0
        //
        // Language subtag canonicalization methods
        //

        public static string CanonicalizeLanguage(string s)
        {
            return(AsciiUtil.ToLower(s));
        }
 public static bool IsAttribute(string s)
 {
     // 3*8alphanum
     return((s.Length >= 3) && (s.Length <= 8) && AsciiUtil.IsAlphaNumericString(s));
 }
Beispiel #14
0
 public static bool IsPrivateusePrefixChar(char c)
 {
     return(AsciiUtil.CaseIgnoreMatch(PRIVATEUSE, new string(new char[] { c })));
 }
Beispiel #15
0
 public override int GetHashCode()
 {
     return(AsciiUtil.ToLower(_c));
 }
Beispiel #16
0
 public static bool IsPrivateuseSubtag(string s)
 {
     // privateuse    = "x" 1*("-" (1*8alphanum))
     return((s.Length >= 1) && (s.Length <= 8) && AsciiUtil.IsAlphaNumericString(s));
 }
Beispiel #17
0
 public AsciiCaseInsensitiveKey(string key)
 {
     _key  = key;
     _hash = AsciiUtil.ToLower(key).GetHashCode();
 }
Beispiel #18
0
 public static string CanonicalizeScript(string s)
 {
     return(AsciiUtil.ToTitleString(s));
 }
Beispiel #19
0
        public static LanguageTag ParseLocale(BaseLocale baseLocale, LocaleExtensions localeExtensions)
        {
            LanguageTag tag = new LanguageTag();

            string language = baseLocale.GetLanguage();
            string script   = baseLocale.GetScript();
            string region   = baseLocale.GetRegion();
            string variant  = baseLocale.GetVariant();

            bool hasSubtag = false;

            string privuseVar = null;   // store ill-formed variant subtags

            if (language.Length > 0 && IsLanguage(language))
            {
                // Convert a deprecated language code used by Java to
                // a new code
                if (language.Equals("iw"))
                {
                    language = "he";
                }
                else if (language.Equals("ji"))
                {
                    language = "yi";
                }
                else if (language.Equals("in"))
                {
                    language = "id";
                }
                tag._language = language;
            }

            if (script.Length > 0 && IsScript(script))
            {
                tag._script = CanonicalizeScript(script);
                hasSubtag   = true;
            }

            if (region.Length > 0 && IsRegion(region))
            {
                tag._region = CanonicalizeRegion(region);
                hasSubtag   = true;
            }

            // ICU4N TODO: Remove ?
            if (JDKIMPL)
            {
                // Special handling for no_NO_NY - use nn_NO for language tag
                if (tag._language.Equals("no") && tag._region.Equals("NO") && variant.Equals("NY")) // ICU4N TODO: Fix this handling for .NET (no-NO is not reliable across platforms)
                {
                    tag._language = "nn";
                    variant       = "";
                }
            }

            if (variant.Length > 0)
            {
                List <string>         variants = null;
                StringTokenEnumerator varitr   = new StringTokenEnumerator(variant, BaseLocale.SEP);
                while (varitr.MoveNext())
                {
                    string var = varitr.Current;
                    if (!IsVariant(var))
                    {
                        break;
                    }
                    if (variants == null)
                    {
                        variants = new List <string>();
                    }
                    if (JDKIMPL)
                    {
                        variants.Add(var);  // Do not canonicalize!
                    }
                    else
                    {
                        variants.Add(CanonicalizeVariant(var));
                    }
                }
                if (variants != null)
                {
                    tag._variants = variants;
                    hasSubtag     = true;
                }
                if (!varitr.IsDone)
                {
                    // ill-formed variant subtags
                    StringBuilder buf = new StringBuilder();
                    while (!varitr.IsDone)
                    {
                        string prvv = varitr.Current;
                        if (!IsPrivateuseSubtag(prvv))
                        {
                            // cannot use private use subtag - truncated
                            break;
                        }
                        if (buf.Length > 0)
                        {
                            buf.Append(SEP);
                        }
                        if (!JDKIMPL)
                        {
                            prvv = AsciiUtil.ToLowerString(prvv);
                        }
                        buf.Append(prvv);
                        varitr.MoveNext();
                    }
                    if (buf.Length > 0)
                    {
                        privuseVar = buf.ToString();
                    }
                }
            }

            List <string> extensions = null;
            string        privateuse = null;

            var locextKeys = localeExtensions.Keys;

            foreach (char locextKey in locextKeys)
            {
                Extension ext = localeExtensions.GetExtension(locextKey);
                if (IsPrivateusePrefixChar(locextKey))
                {
                    privateuse = ext.Value;
                }
                else
                {
                    if (extensions == null)
                    {
                        extensions = new List <string>();
                    }
                    extensions.Add(locextKey.ToString() + SEP + ext.Value);
                }
            }

            if (extensions != null)
            {
                tag._extensions = extensions;
                hasSubtag       = true;
            }

            // append ill-formed variant subtags to private use
            if (privuseVar != null)
            {
                if (privateuse == null)
                {
                    privateuse = PRIVUSE_VARIANT_PREFIX + SEP + privuseVar;
                }
                else
                {
                    privateuse = privateuse + SEP + PRIVUSE_VARIANT_PREFIX + SEP + privuseVar.Replace(BaseLocale.SEP, SEP);
                }
            }

            if (privateuse != null)
            {
                tag._privateuse = privateuse;
            }

            if (tag._language.Length == 0 && (hasSubtag || privateuse == null))
            {
                // use lang "und" when 1) no language is available AND
                // 2) any of other subtags other than private use are available or
                // no private use tag is available
                tag._language = UNDETERMINED;
            }

            return(tag);
        }
Beispiel #20
0
 public static string CanonicalizeVariant(string s)
 {
     return(AsciiUtil.ToLowerString(s));
 }
Beispiel #21
0
 public static bool IsExtlang(string s)
 {
     // extlang       = 3ALPHA              ; selected ISO 639 codes
     //                 *2("-" 3ALPHA)      ; permanently reserved
     return((s.Length == 3) && AsciiUtil.IsAlphaString(s));
 }
Beispiel #22
0
 public static string CanonicalizePrivateuseSubtag(string s)
 {
     return(AsciiUtil.ToLowerString(s));
 }
Beispiel #23
0
 public static bool IsScript(String s)
 {
     // script        = 4ALPHA              ; ISO 15924 code
     return((s.Length == 4) && AsciiUtil.IsAlphaString(s));
 }
Beispiel #24
0
            internal abstract bool IsWellFormed(string value); // doesn't test validity, just whether it is well formed.

            internal string Canonicalize(string value)
            {
                return(AsciiUtil.ToLower(value));
            }
Beispiel #25
0
 public static bool IsExtensionSubtag(string s)
 {
     // extension     = singleton 1*("-" (2*8alphanum))
     return((s.Length >= 2) && (s.Length <= 8) && AsciiUtil.IsAlphaNumericString(s));
 }
Beispiel #26
0
 public static bool IsSingletonChar(char c)
 {
     return(Singleton == AsciiUtil.ToLower(c));
 }
Beispiel #27
0
 public static bool IsPrivateusePrefix(string s)
 {
     // privateuse    = "x" 1*("-" (1*8alphanum))
     return((s.Length == 1) &&
            AsciiUtil.CaseIgnoreMatch(PRIVATEUSE, s));
 }
Beispiel #28
0
 public static bool IsTypeSubtag(string s)
 {
     // 3*8alphanum
     return((s.Length >= 3) && (s.Length <= 8) && AsciiUtil.IsAlphaNumeric(s));
 }
 internal CaseInsensitiveString(string s)
 {
     _s    = s;
     _hash = AsciiUtil.ToLower(_s).GetHashCode(); // ICU4N specific - cache hash code, since this is an immutable object
 }