• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GENERATED SOURCE. DO NOT MODIFY. */
2 // © 2016 and later: Unicode, Inc. and others.
3 // License & terms of use: http://www.unicode.org/copyright.html#License
4 /*
5  *******************************************************************************
6  * Copyright (C) 2009-2010, International Business Machines Corporation and    *
7  * others. All Rights Reserved.                                                *
8  *******************************************************************************
9  */
10 package ohos.global.icu.impl.locale;
11 
12 import java.util.ArrayList;
13 import java.util.Collections;
14 import java.util.HashMap;
15 import java.util.HashSet;
16 import java.util.List;
17 import java.util.Set;
18 
19 /**
20  * @hide exposed on OHOS
21  */
22 public final class InternalLocaleBuilder {
23 
24     private static final boolean JDKIMPL = false;
25 
26     private String _language = "";
27     private String _script = "";
28     private String _region = "";
29     private String _variant = "";
30 
31     private static final CaseInsensitiveChar PRIVUSE_KEY = new CaseInsensitiveChar(LanguageTag.PRIVATEUSE.charAt(0));
32 
33     private HashMap<CaseInsensitiveChar, String> _extensions;
34     private HashSet<CaseInsensitiveString> _uattributes;
35     private HashMap<CaseInsensitiveString, String> _ukeywords;
36 
37 
InternalLocaleBuilder()38     public InternalLocaleBuilder() {
39     }
40 
setLanguage(String language)41     public InternalLocaleBuilder setLanguage(String language) throws LocaleSyntaxException {
42         if (language == null || language.length() == 0) {
43             _language = "";
44         } else {
45             if (!LanguageTag.isLanguage(language)) {
46                 throw new LocaleSyntaxException("Ill-formed language: " + language, 0);
47             }
48             _language = language;
49         }
50         return this;
51     }
52 
setScript(String script)53     public InternalLocaleBuilder setScript(String script) throws LocaleSyntaxException {
54         if (script == null || script.length() == 0) {
55             _script = "";
56         } else {
57             if (!LanguageTag.isScript(script)) {
58                 throw new LocaleSyntaxException("Ill-formed script: " + script, 0);
59             }
60             _script = script;
61         }
62         return this;
63     }
64 
setRegion(String region)65     public InternalLocaleBuilder setRegion(String region) throws LocaleSyntaxException {
66         if (region == null || region.length() == 0) {
67             _region = "";
68         } else {
69             if (!LanguageTag.isRegion(region)) {
70                 throw new LocaleSyntaxException("Ill-formed region: " + region, 0);
71             }
72             _region = region;
73         }
74         return this;
75     }
76 
setVariant(String variant)77     public InternalLocaleBuilder setVariant(String variant) throws LocaleSyntaxException {
78         if (variant == null || variant.length() == 0) {
79             _variant = "";
80         } else {
81             // normalize separators to "_"
82             String var = variant.replaceAll(LanguageTag.SEP, BaseLocale.SEP);
83             int errIdx = checkVariants(var, BaseLocale.SEP);
84             if (errIdx != -1) {
85                 throw new LocaleSyntaxException("Ill-formed variant: " + variant, errIdx);
86             }
87             _variant = var;
88         }
89         return this;
90     }
91 
addUnicodeLocaleAttribute(String attribute)92     public InternalLocaleBuilder addUnicodeLocaleAttribute(String attribute) throws LocaleSyntaxException {
93         if (attribute == null || !UnicodeLocaleExtension.isAttribute(attribute)) {
94             throw new LocaleSyntaxException("Ill-formed Unicode locale attribute: " + attribute);
95         }
96         // Use case insensitive string to prevent duplication
97         if (_uattributes == null) {
98             _uattributes = new HashSet<CaseInsensitiveString>(4);
99         }
100         _uattributes.add(new CaseInsensitiveString(attribute));
101         return this;
102     }
103 
removeUnicodeLocaleAttribute(String attribute)104     public InternalLocaleBuilder removeUnicodeLocaleAttribute(String attribute) throws LocaleSyntaxException {
105         if (attribute == null || !UnicodeLocaleExtension.isAttribute(attribute)) {
106             throw new LocaleSyntaxException("Ill-formed Unicode locale attribute: " + attribute);
107         }
108         if (_uattributes != null) {
109             _uattributes.remove(new CaseInsensitiveString(attribute));
110         }
111         return this;
112     }
113 
setUnicodeLocaleKeyword(String key, String type)114     public InternalLocaleBuilder setUnicodeLocaleKeyword(String key, String type) throws LocaleSyntaxException {
115         if (!UnicodeLocaleExtension.isKey(key)) {
116             throw new LocaleSyntaxException("Ill-formed Unicode locale keyword key: " + key);
117         }
118 
119         CaseInsensitiveString cikey = new CaseInsensitiveString(key);
120         if (type == null) {
121             if (_ukeywords != null) {
122                 // null type is used for remove the key
123                 _ukeywords.remove(cikey);
124             }
125         } else {
126             if (type.length() != 0) {
127                 // normalize separator to "-"
128                 String tp = type.replaceAll(BaseLocale.SEP, LanguageTag.SEP);
129                 // validate
130                 StringTokenIterator itr = new StringTokenIterator(tp, LanguageTag.SEP);
131                 while (!itr.isDone()) {
132                     String s = itr.current();
133                     if (!UnicodeLocaleExtension.isTypeSubtag(s)) {
134                         throw new LocaleSyntaxException("Ill-formed Unicode locale keyword type: " + type, itr.currentStart());
135                     }
136                     itr.next();
137                 }
138             }
139             if (_ukeywords == null) {
140                 _ukeywords = new HashMap<CaseInsensitiveString, String>(4);
141             }
142             _ukeywords.put(cikey, type);
143         }
144         return this;
145     }
146 
setExtension(char singleton, String value)147     public InternalLocaleBuilder setExtension(char singleton, String value) throws LocaleSyntaxException {
148         // validate key
149         boolean isBcpPrivateuse = LanguageTag.isPrivateusePrefixChar(singleton);
150         if (!isBcpPrivateuse && !LanguageTag.isExtensionSingletonChar(singleton)) {
151             throw new LocaleSyntaxException("Ill-formed extension key: " + singleton);
152         }
153 
154         boolean remove = (value == null || value.length() == 0);
155         CaseInsensitiveChar key = new CaseInsensitiveChar(singleton);
156 
157         if (remove) {
158             if (UnicodeLocaleExtension.isSingletonChar(key.value())) {
159                 // clear entire Unicode locale extension
160                 if (_uattributes != null) {
161                     _uattributes.clear();
162                 }
163                 if (_ukeywords != null) {
164                     _ukeywords.clear();
165                 }
166             } else {
167                 if (_extensions != null && _extensions.containsKey(key)) {
168                     _extensions.remove(key);
169                 }
170             }
171         } else {
172             // validate value
173             String val = value.replaceAll(BaseLocale.SEP, LanguageTag.SEP);
174             StringTokenIterator itr = new StringTokenIterator(val, LanguageTag.SEP);
175             while (!itr.isDone()) {
176                 String s = itr.current();
177                 boolean validSubtag;
178                 if (isBcpPrivateuse) {
179                     validSubtag = LanguageTag.isPrivateuseSubtag(s);
180                 } else {
181                     validSubtag = LanguageTag.isExtensionSubtag(s);
182                 }
183                 if (!validSubtag) {
184                     throw new LocaleSyntaxException("Ill-formed extension value: " + s, itr.currentStart());
185                 }
186                 itr.next();
187             }
188 
189             if (UnicodeLocaleExtension.isSingletonChar(key.value())) {
190                 setUnicodeLocaleExtension(val);
191             } else {
192                 if (_extensions == null) {
193                     _extensions = new HashMap<CaseInsensitiveChar, String>(4);
194                 }
195                 _extensions.put(key, val);
196             }
197         }
198         return this;
199     }
200 
201     /*
202      * Set extension/private subtags in a single string representation
203      */
setExtensions(String subtags)204     public InternalLocaleBuilder setExtensions(String subtags) throws LocaleSyntaxException {
205         if (subtags == null || subtags.length() == 0) {
206             clearExtensions();
207             return this;
208         }
209         subtags = subtags.replaceAll(BaseLocale.SEP, LanguageTag.SEP);
210         StringTokenIterator itr = new StringTokenIterator(subtags, LanguageTag.SEP);
211 
212         List<String> extensions = null;
213         String privateuse = null;
214 
215         int parsed = 0;
216         int start;
217 
218         // Make a list of extension subtags
219         while (!itr.isDone()) {
220             String s = itr.current();
221             if (LanguageTag.isExtensionSingleton(s)) {
222                 start = itr.currentStart();
223                 String singleton = s;
224                 StringBuilder sb = new StringBuilder(singleton);
225 
226                 itr.next();
227                 while (!itr.isDone()) {
228                     s = itr.current();
229                     if (LanguageTag.isExtensionSubtag(s)) {
230                         sb.append(LanguageTag.SEP).append(s);
231                         parsed = itr.currentEnd();
232                     } else {
233                         break;
234                     }
235                     itr.next();
236                 }
237 
238                 if (parsed < start) {
239                     throw new LocaleSyntaxException("Incomplete extension '" + singleton + "'", start);
240                 }
241 
242                 if (extensions == null) {
243                     extensions = new ArrayList<String>(4);
244                 }
245                 extensions.add(sb.toString());
246             } else {
247                 break;
248             }
249         }
250         if (!itr.isDone()) {
251             String s = itr.current();
252             if (LanguageTag.isPrivateusePrefix(s)) {
253                 start = itr.currentStart();
254                 StringBuilder sb = new StringBuilder(s);
255 
256                 itr.next();
257                 while (!itr.isDone()) {
258                     s = itr.current();
259                     if (!LanguageTag.isPrivateuseSubtag(s)) {
260                         break;
261                     }
262                     sb.append(LanguageTag.SEP).append(s);
263                     parsed = itr.currentEnd();
264 
265                     itr.next();
266                 }
267                 if (parsed <= start) {
268                     throw new LocaleSyntaxException("Incomplete privateuse:" + subtags.substring(start), start);
269                 } else {
270                     privateuse = sb.toString();
271                 }
272             }
273         }
274 
275         if (!itr.isDone()) {
276             throw new LocaleSyntaxException("Ill-formed extension subtags:" + subtags.substring(itr.currentStart()), itr.currentStart());
277         }
278 
279         return setExtensions(extensions, privateuse);
280     }
281 
282     /*
283      * Set a list of BCP47 extensions and private use subtags
284      * BCP47 extensions are already validated and well-formed, but may contain duplicates
285      */
setExtensions(List<String> bcpExtensions, String privateuse)286     private InternalLocaleBuilder setExtensions(List<String> bcpExtensions, String privateuse) {
287         clearExtensions();
288 
289         if (bcpExtensions != null && bcpExtensions.size() > 0) {
290             HashSet<CaseInsensitiveChar> processedExtensions = new HashSet<CaseInsensitiveChar>(bcpExtensions.size());
291             for (String bcpExt : bcpExtensions) {
292                 CaseInsensitiveChar key = new CaseInsensitiveChar(bcpExt.charAt(0));
293                 // ignore duplicates
294                 if (!processedExtensions.contains(key)) {
295                     // each extension string contains singleton, e.g. "a-abc-def"
296                     if (UnicodeLocaleExtension.isSingletonChar(key.value())) {
297                         setUnicodeLocaleExtension(bcpExt.substring(2));
298                     } else {
299                         if (_extensions == null) {
300                             _extensions = new HashMap<CaseInsensitiveChar, String>(4);
301                         }
302                         _extensions.put(key, bcpExt.substring(2));
303                     }
304                 }
305             }
306         }
307         if (privateuse != null && privateuse.length() > 0) {
308             // privateuse string contains prefix, e.g. "x-abc-def"
309             if (_extensions == null) {
310                 _extensions = new HashMap<CaseInsensitiveChar, String>(1);
311             }
312             _extensions.put(new CaseInsensitiveChar(privateuse.charAt(0)), privateuse.substring(2));
313         }
314 
315         return this;
316     }
317 
318     /*
319      * Reset Builder's internal state with the given language tag
320      */
setLanguageTag(LanguageTag langtag)321     public InternalLocaleBuilder setLanguageTag(LanguageTag langtag) {
322         clear();
323         if (langtag.getExtlangs().size() > 0) {
324             _language = langtag.getExtlangs().get(0);
325         } else {
326             String language = langtag.getLanguage();
327             if (!language.equals(LanguageTag.UNDETERMINED)) {
328                 _language = language;
329             }
330         }
331         _script = langtag.getScript();
332         _region = langtag.getRegion();
333 
334         ArrayList<String> bcpVariants = new ArrayList<String>(langtag.getVariants());
335         Collections.sort(bcpVariants);
336         if (bcpVariants.size() > 0) {
337             StringBuilder var = new StringBuilder(bcpVariants.get(0));
338             for (int i = 1; i < bcpVariants.size(); i++) {
339                 var.append(BaseLocale.SEP).append(bcpVariants.get(i));
340             }
341             _variant = var.toString();
342         }
343 
344         setExtensions(langtag.getExtensions(), langtag.getPrivateuse());
345 
346         return this;
347     }
348 
setLocale(BaseLocale base, LocaleExtensions extensions)349     public InternalLocaleBuilder setLocale(BaseLocale base, LocaleExtensions extensions) throws LocaleSyntaxException {
350         String language = base.getLanguage();
351         String script = base.getScript();
352         String region = base.getRegion();
353         String variant = base.getVariant();
354 
355         if (JDKIMPL) {
356             // Special backward compatibility support
357 
358             // Exception 1 - ja_JP_JP
359             if (language.equals("ja") && region.equals("JP") && variant.equals("JP")) {
360                 // When locale ja_JP_JP is created, ca-japanese is always there.
361                 // The builder ignores the variant "JP"
362                 assert("japanese".equals(extensions.getUnicodeLocaleType("ca")));
363                 variant = "";
364             }
365             // Exception 2 - th_TH_TH
366             else if (language.equals("th") && region.equals("TH") && variant.equals("TH")) {
367                 // When locale th_TH_TH is created, nu-thai is always there.
368                 // The builder ignores the variant "TH"
369                 assert("thai".equals(extensions.getUnicodeLocaleType("nu")));
370                 variant = "";
371             }
372             // Exception 3 - no_NO_NY
373             else if (language.equals("no") && region.equals("NO") && variant.equals("NY")) {
374                 // no_NO_NY is a valid locale and used by Java 6 or older versions.
375                 // The build ignores the variant "NY" and change the language to "nn".
376                 language = "nn";
377                 variant = "";
378             }
379         }
380 
381         // Validate base locale fields before updating internal state.
382         // LocaleExtensions always store validated/canonicalized values,
383         // so no checks are necessary.
384         if (language.length() > 0 && !LanguageTag.isLanguage(language)) {
385             throw new LocaleSyntaxException("Ill-formed language: " + language);
386         }
387 
388         if (script.length() > 0 && !LanguageTag.isScript(script)) {
389             throw new LocaleSyntaxException("Ill-formed script: " + script);
390         }
391 
392         if (region.length() > 0 && !LanguageTag.isRegion(region)) {
393             throw new LocaleSyntaxException("Ill-formed region: " + region);
394         }
395 
396         if (variant.length() > 0) {
397             int errIdx = checkVariants(variant, BaseLocale.SEP);
398             if (errIdx != -1) {
399                 throw new LocaleSyntaxException("Ill-formed variant: " + variant, errIdx);
400             }
401         }
402 
403         // The input locale is validated at this point.
404         // Now, updating builder's internal fields.
405         _language = language;
406         _script = script;
407         _region = region;
408         _variant = variant;
409         clearExtensions();
410 
411         Set<Character> extKeys = (extensions == null) ? null : extensions.getKeys();
412         if (extKeys != null) {
413             // map extensions back to builder's internal format
414             for (Character key : extKeys) {
415                 Extension e = extensions.getExtension(key);
416                 if (e instanceof UnicodeLocaleExtension) {
417                     UnicodeLocaleExtension ue = (UnicodeLocaleExtension)e;
418                     for (String uatr : ue.getUnicodeLocaleAttributes()) {
419                         if (_uattributes == null) {
420                             _uattributes = new HashSet<CaseInsensitiveString>(4);
421                         }
422                         _uattributes.add(new CaseInsensitiveString(uatr));
423                     }
424                     for (String ukey : ue.getUnicodeLocaleKeys()) {
425                         if (_ukeywords == null) {
426                             _ukeywords = new HashMap<CaseInsensitiveString, String>(4);
427                         }
428                         _ukeywords.put(new CaseInsensitiveString(ukey), ue.getUnicodeLocaleType(ukey));
429                     }
430                 } else {
431                     if (_extensions == null) {
432                         _extensions = new HashMap<CaseInsensitiveChar, String>(4);
433                     }
434                     _extensions.put(new CaseInsensitiveChar(key.charValue()), e.getValue());
435                 }
436             }
437         }
438         return this;
439     }
440 
clear()441     public InternalLocaleBuilder clear() {
442         _language = "";
443         _script = "";
444         _region = "";
445         _variant = "";
446         clearExtensions();
447         return this;
448     }
449 
clearExtensions()450     public InternalLocaleBuilder clearExtensions() {
451         if (_extensions != null) {
452             _extensions.clear();
453         }
454         if (_uattributes != null) {
455             _uattributes.clear();
456         }
457         if (_ukeywords != null) {
458             _ukeywords.clear();
459         }
460         return this;
461     }
462 
getBaseLocale()463     public BaseLocale getBaseLocale() {
464         String language = _language;
465         String script = _script;
466         String region = _region;
467         String variant = _variant;
468 
469         // Special private use subtag sequence identified by "lvariant" will be
470         // interpreted as Java variant.
471         if (_extensions != null) {
472             String privuse = _extensions.get(PRIVUSE_KEY);
473             if (privuse != null) {
474                 StringTokenIterator itr = new StringTokenIterator(privuse, LanguageTag.SEP);
475                 boolean sawPrefix = false;
476                 int privVarStart = -1;
477                 while (!itr.isDone()) {
478                     if (sawPrefix) {
479                         privVarStart = itr.currentStart();
480                         break;
481                     }
482                     if (AsciiUtil.caseIgnoreMatch(itr.current(), LanguageTag.PRIVUSE_VARIANT_PREFIX)) {
483                         sawPrefix = true;
484                     }
485                     itr.next();
486                 }
487                 if (privVarStart != -1) {
488                     StringBuilder sb = new StringBuilder(variant);
489                     if (sb.length() != 0) {
490                         sb.append(BaseLocale.SEP);
491                     }
492                     sb.append(privuse.substring(privVarStart).replaceAll(LanguageTag.SEP, BaseLocale.SEP));
493                     variant = sb.toString();
494                 }
495             }
496         }
497 
498         return BaseLocale.getInstance(language, script, region, variant);
499     }
500 
getLocaleExtensions()501     public LocaleExtensions getLocaleExtensions() {
502         if ((_extensions == null || _extensions.size() == 0)
503                 && (_uattributes == null || _uattributes.size() == 0)
504                 && (_ukeywords == null || _ukeywords.size() == 0)) {
505             return LocaleExtensions.EMPTY_EXTENSIONS;
506         }
507 
508         return new LocaleExtensions(_extensions, _uattributes, _ukeywords);
509     }
510 
511     /*
512      * Remove special private use subtag sequence identified by "lvariant"
513      * and return the rest. Only used by LocaleExtensions
514      */
removePrivateuseVariant(String privuseVal)515     static String removePrivateuseVariant(String privuseVal) {
516         StringTokenIterator itr = new StringTokenIterator(privuseVal, LanguageTag.SEP);
517 
518         // Note: privateuse value "abc-lvariant" is unchanged
519         // because no subtags after "lvariant".
520 
521         int prefixStart = -1;
522         boolean sawPrivuseVar = false;
523         while (!itr.isDone()) {
524             if (prefixStart != -1) {
525                 // Note: privateuse value "abc-lvariant" is unchanged
526                 // because no subtags after "lvariant".
527                 sawPrivuseVar = true;
528                 break;
529             }
530             if (AsciiUtil.caseIgnoreMatch(itr.current(), LanguageTag.PRIVUSE_VARIANT_PREFIX)) {
531                 prefixStart = itr.currentStart();
532             }
533             itr.next();
534         }
535         if (!sawPrivuseVar) {
536             return privuseVal;
537         }
538 
539         assert(prefixStart == 0 || prefixStart > 1);
540         return (prefixStart == 0) ? null : privuseVal.substring(0, prefixStart -1);
541     }
542 
543     /*
544      * Check if the given variant subtags separated by the given
545      * separator(s) are valid
546      */
checkVariants(String variants, String sep)547     private int checkVariants(String variants, String sep) {
548         StringTokenIterator itr = new StringTokenIterator(variants, sep);
549         while (!itr.isDone()) {
550             String s = itr.current();
551             if (!LanguageTag.isVariant(s)) {
552                 return itr.currentStart();
553             }
554             itr.next();
555         }
556         return -1;
557     }
558 
559     /*
560      * Private methods parsing Unicode Locale Extension subtags.
561      * Duplicated attributes/keywords will be ignored.
562      * The input must be a valid extension subtags (excluding singleton).
563      */
setUnicodeLocaleExtension(String subtags)564     private void setUnicodeLocaleExtension(String subtags) {
565         // wipe out existing attributes/keywords
566         if (_uattributes != null) {
567             _uattributes.clear();
568         }
569         if (_ukeywords != null) {
570             _ukeywords.clear();
571         }
572 
573         StringTokenIterator itr = new StringTokenIterator(subtags, LanguageTag.SEP);
574 
575         // parse attributes
576         while (!itr.isDone()) {
577             if (!UnicodeLocaleExtension.isAttribute(itr.current())) {
578                 break;
579             }
580             if (_uattributes == null) {
581                 _uattributes = new HashSet<CaseInsensitiveString>(4);
582             }
583             _uattributes.add(new CaseInsensitiveString(itr.current()));
584             itr.next();
585         }
586 
587         // parse keywords
588         CaseInsensitiveString key = null;
589         String type;
590         int typeStart = -1;
591         int typeEnd = -1;
592         while (!itr.isDone()) {
593             if (key != null) {
594                 if (UnicodeLocaleExtension.isKey(itr.current())) {
595                     // next keyword - emit previous one
596                     assert(typeStart == -1 || typeEnd != -1);
597                     type = (typeStart == -1) ? "" : subtags.substring(typeStart, typeEnd);
598                     if (_ukeywords == null) {
599                         _ukeywords = new HashMap<CaseInsensitiveString, String>(4);
600                     }
601                     _ukeywords.put(key, type);
602 
603                     // reset keyword info
604                     CaseInsensitiveString tmpKey = new CaseInsensitiveString(itr.current());
605                     key = _ukeywords.containsKey(tmpKey) ? null : tmpKey;
606                     typeStart = typeEnd = -1;
607                 } else {
608                     if (typeStart == -1) {
609                         typeStart = itr.currentStart();
610                     }
611                     typeEnd = itr.currentEnd();
612                 }
613             } else if (UnicodeLocaleExtension.isKey(itr.current())) {
614                 // 1. first keyword or
615                 // 2. next keyword, but previous one was duplicate
616                 key = new CaseInsensitiveString(itr.current());
617                 if (_ukeywords != null && _ukeywords.containsKey(key)) {
618                     // duplicate
619                     key = null;
620                 }
621             }
622 
623             if (!itr.hasNext()) {
624                 if (key != null) {
625                     // last keyword
626                     assert(typeStart == -1 || typeEnd != -1);
627                     type = (typeStart == -1) ? "" : subtags.substring(typeStart, typeEnd);
628                     if (_ukeywords == null) {
629                         _ukeywords = new HashMap<CaseInsensitiveString, String>(4);
630                     }
631                     _ukeywords.put(key, type);
632                 }
633                 break;
634             }
635 
636             itr.next();
637         }
638     }
639 
640     static class CaseInsensitiveString {
641         private String _s;
642 
CaseInsensitiveString(String s)643         CaseInsensitiveString(String s) {
644             _s = s;
645         }
646 
value()647         public String value() {
648             return _s;
649         }
650 
651         @Override
hashCode()652         public int hashCode() {
653             return AsciiUtil.toLowerString(_s).hashCode();
654         }
655 
656         @Override
equals(Object obj)657         public boolean equals(Object obj) {
658             if (this == obj) {
659                 return true;
660             }
661             if (!(obj instanceof CaseInsensitiveString)) {
662                 return false;
663             }
664             return AsciiUtil.caseIgnoreMatch(_s, ((CaseInsensitiveString)obj).value());
665         }
666     }
667 
668     static class CaseInsensitiveChar {
669         private char _c;
670 
CaseInsensitiveChar(char c)671         CaseInsensitiveChar(char c) {
672             _c = c;
673         }
674 
value()675         public char value() {
676             return _c;
677         }
678 
679         @Override
hashCode()680         public int hashCode() {
681             return AsciiUtil.toLower(_c);
682         }
683 
684         @Override
equals(Object obj)685         public boolean equals(Object obj) {
686             if (this == obj) {
687                 return true;
688             }
689             if (!(obj instanceof CaseInsensitiveChar)) {
690                 return false;
691             }
692             return _c ==  AsciiUtil.toLower(((CaseInsensitiveChar)obj).value());
693         }
694 
695     }
696 }
697