• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // © 2022 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 package com.ibm.icu.impl.personname;
4 
5 import java.util.Locale;
6 import java.util.StringTokenizer;
7 
8 import com.ibm.icu.lang.UCharacter;
9 import com.ibm.icu.text.BreakIterator;
10 import com.ibm.icu.text.CaseMap;
11 import com.ibm.icu.text.PersonName;
12 import com.ibm.icu.text.SimpleFormatter;
13 
14 /**
15  * Parent class for classes that implement field-modifier behavior.
16  */
17 abstract class FieldModifierImpl {
modifyField(String fieldValue)18     public abstract String modifyField(String fieldValue);
19 
forName(PersonName.FieldModifier modifierID, PersonNameFormatterImpl formatterImpl)20     public static FieldModifierImpl forName(PersonName.FieldModifier modifierID, PersonNameFormatterImpl formatterImpl) {
21         switch (modifierID) {
22             case INFORMAL:
23                 return NOOP_MODIFIER;
24             case PREFIX:
25                 return NULL_MODIFIER;
26             case CORE:
27                 return NOOP_MODIFIER;
28             case ALL_CAPS:
29                 return new AllCapsModifier(formatterImpl.getLocale());
30             case INITIAL_CAP:
31                 return new InitialCapModifier(formatterImpl.getLocale());
32             case INITIAL:
33                 return new InitialModifier(formatterImpl.getInitialPattern(), formatterImpl.getInitialSequencePattern());
34             case MONOGRAM:
35                 return MONOGRAM_MODIFIER;
36             default:
37                 throw new IllegalArgumentException("Invalid modifier ID " + modifierID);
38         }
39     }
40 
41     /**
42      * A field modifier that just returns the field value unmodified.  This is used to implement the default
43      * behavior of the "informal" and "core" modifiers ("real" informal or core variants have to be supplied or
44      * calculated by the PersonName object).
45      */
46     private static final FieldModifierImpl NOOP_MODIFIER = new FieldModifierImpl() {
47         @Override
48         public String modifyField(String fieldValue) {
49             return fieldValue;
50         }
51     };
52 
53     /**
54      * A field modifier that just returns the empty string.  This is used to implement the default behavior of the
55      * "prefix" modifier ("real" prefix variants have to be supplied to calculated by the PersonName object).
56      */
57     private static final FieldModifierImpl NULL_MODIFIER = new FieldModifierImpl() {
58         @Override
59         public String modifyField(String fieldValue) {
60             return "";
61         }
62     };
63 
64     /**
65      * A field modifier that returns the field value converted to ALL CAPS.  This is the default behavior
66      * for the "allCaps" modifier.
67      */
68     private static class AllCapsModifier extends FieldModifierImpl {
69         private final Locale locale;
70 
AllCapsModifier(Locale locale)71         public AllCapsModifier(Locale locale) {
72             this.locale = locale;
73         }
74 
75         @Override
modifyField(String fieldValue)76         public String modifyField(String fieldValue) {
77             return UCharacter.toUpperCase(locale, fieldValue);
78         }
79     }
80 
81     /**
82      * A field modifier that returns the field value with the first letter of each word capitalized.  This is
83      * the default behavior of the "initialCap" modifier.
84      */
85     private static class InitialCapModifier extends FieldModifierImpl {
86         private final Locale locale;
87         private static final CaseMap.Title TO_TITLE_WHOLE_STRING_NO_LOWERCASE = CaseMap.toTitle().wholeString().noLowercase();
88 
InitialCapModifier(Locale locale)89         public InitialCapModifier(Locale locale) {
90             this.locale = locale;
91         }
92 
93         @Override
modifyField(String fieldValue)94         public String modifyField(String fieldValue) {
95             return TO_TITLE_WHOLE_STRING_NO_LOWERCASE.apply(locale, null, fieldValue);
96         }
97     }
98 
99     /**
100      * A field modifier that returns the field value converted into one or more initials.  This is the first grapheme
101      * cluster of each word in the field value, modified using the initialPattern/initial resource value from the
102      * locale data, and strung together using the initialPattern/initialSequence resource value from the locale data.
103      * (In English, these patterns put periods after each initial and connect them with spaces.)
104      * This is default behavior of the "initial" modifier.
105      */
106     private static class InitialModifier extends FieldModifierImpl {
107         private final SimpleFormatter initialFormatter;
108         private final SimpleFormatter initialSequenceFormatter;
109 
InitialModifier(String initialPattern, String initialSequencePattern)110         public InitialModifier(String initialPattern, String initialSequencePattern) {
111             this.initialFormatter = SimpleFormatter.compile(initialPattern);
112             this.initialSequenceFormatter = SimpleFormatter.compile(initialSequencePattern);
113         }
114 
115         @Override
modifyField(String fieldValue)116         public String modifyField(String fieldValue) {
117             String result = null;
118             StringTokenizer tok = new StringTokenizer(fieldValue, " ");
119             while (tok.hasMoreTokens()) {
120                 String curInitial = getFirstGrapheme(tok.nextToken());
121                 if (result == null) {
122                     result = initialFormatter.format(curInitial);
123                 } else {
124                     result = initialSequenceFormatter.format(result, initialFormatter.format(curInitial));
125                 }
126             }
127             return result;
128         }
129     }
130 
131     /**
132      * A field modifier that simply returns the first grapheme cluster in the field value.
133      * This is the default implementation of the "monogram" modifier.
134      */
135     private static final FieldModifierImpl MONOGRAM_MODIFIER = new FieldModifierImpl() {
136         @Override
137         public String modifyField(String fieldValue) {
138             return getFirstGrapheme(fieldValue);
139         }
140     };
141 
142     /**
143      * A utility function that just returns the first grapheme cluster in the string.
144      */
getFirstGrapheme(String s)145     private static String getFirstGrapheme(String s) {
146         // early out if the string is empty to avoid StringIndexOutOfBoundsException
147         if (s.isEmpty()) {
148             return "";
149         }
150 
151         // (currently, no locale overrides the grapheme-break rules, so we just use "root" instead of passing in the locale)
152         BreakIterator bi = BreakIterator.getCharacterInstance(Locale.ROOT);
153         bi.setText(s);
154         return s.substring(0, bi.next());
155     }
156 }
157